フリープログラマー日記

iOS,アンドロイド開発を気ままにしながら生きてるおじさんのブログです。

第40回 昔はサブルーチン、今は継承です。

 あるクラスの処理と同じことを別のクラスにも書くってよくありますよね。
2つや3つなら問題ないのでしょう。きっと。
 昔、BASICをさわった人ならわかるでしょうが、GOSUBなんてキーワードがありました。今でも、クラス内の処理で関数を作って呼び出すのは、GOSUB感覚ですね。(この文章のターゲットは明らかにBASIC世代です。すみません。)

 で、別クラスの処理が一部同じものだったら、どうしてますか?単純な方法なら、コピペで同じ処理を書いておく・・・ですよね?

f:id:momonga117:20180610114618p:plain

前回の、氏名登録画面ですが、選手クラスのオブジェクトが5個(Entry = true なら黄色背景)、そして、氏名登録ボタンは別クラスです。

実は、枠線部分の描画ルーチンはコピペしてます。

今のままで、動きます。なんの問題もないです。

ちょっとだけ、コード部分を。

import UIKit

class RunnerEntryView: UIView {
    
    /***
     * エントリー用のビュー一人分を作成する
     * 一人につき1個のビューが必要となる。
     */

    
    let myNumber: Int
    private var myName: String        //氏名表示
    private var entry: Bool = false  // 表示用
    
    // Runnerクラスへのアクセス’Handler
    private var entryOk: (() -> Void)?
    private var entryCancel: (() -> Void)?
    
    // 自前のイニシャライザ
    init(number: Int, name: String) {
        myNumber = number
        myName = name
        
        super.init(frame: MySettings.Entry.runnerEntryViewFrame(num: number))
        setBackGroundColor()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // ハンドラーのセッター
    public func setEntryOk(handler: @escaping () -> Void) {
        self.entryOk = handler
    }
    
    public func setEntryCancel(handler: @escaping () -> Void) {
        self.entryCancel = handler
    }
    
    private func setBackGroundColor() {
        // 背景色をセット、エントリー時には色を変える
        if entry {
            backgroundColor = MyColor.selectYellow
        } else {
            backgroundColor = MyColor.unselectedGrey
        }
    }
    
    override func draw(_ rect: CGRect) {
        
        let myWidth = MySettings.Entry.runnerErentryViewWidth()
        let myHeight = MySettings.Entry.runnerErentryViewHeight()
        let lineWidth = myWidth * 0.01
        
        // 番号枠,氏名枠の幅計算
        let numberWidth = myWidth * 0.22
        let nameWidth = myWidth - numberWidth - lineWidth * 3
        
        // コンテキストの取得
        let context = UIGraphicsGetCurrentContext()
    
        // 両サイド
        context?.setLineWidth(lineWidth)
        context?.move(to: CGPoint(x: lineWidth/2, y: 0))
        context?.addLine(to: CGPoint(x: lineWidth/2, y: myHeight))
        context?.move(to: CGPoint(x: myWidth-lineWidth/2, y: 0))
        context?.addLine(to: CGPoint(x: myWidth-lineWidth/2, y: myHeight))
        context?.strokePath()
        
        // 上下サイド
        context?.setLineWidth(lineWidth*0.3)
        context?.move(to: CGPoint(x: 0, y: lineWidth*0.15))
        context?.addLine(to: CGPoint(x: myWidth, y: lineWidth*0.15))
        context?.move(to: CGPoint(x: 0, y: myHeight - lineWidth*0.15))
        context?.addLine(to: CGPoint(x: myWidth, y: myHeight - lineWidth*0.15))
        context?.strokePath()
        
        // 見た目が悪いので、最初の分だけ上の線を太く。
        if myNumber == 1 {
            context?.setLineWidth(lineWidth*0.8)
            context?.move(to: CGPoint(x: 0, y: lineWidth*0.4))
            context?.addLine(to: CGPoint(x: myWidth, y: lineWidth*0.4))
        }
        // 中間の仕切り線
        context?.setLineWidth(lineWidth*0.6)
        context?.move(to: CGPoint(x: numberWidth, y: 0))
        context?.addLine(to: CGPoint(x: numberWidth, y: myHeight))
        context?.strokePath()
        
        // 番号の描画
        let textcolor = myName=="未登録" ? UIColor.lightGray : UIColor.black
        let numCenter: CGPoint = CGPoint(x:numberWidth*0.5, y: myHeight*0.5)
        centerString(string: String(myNumber), charSize: myHeight*0.5, center: numCenter, color: textcolor)
        
        // 氏名の描画
        let nameCenter: CGPoint = CGPoint(x: numberWidth + lineWidth + nameWidth * 0.5, y: myHeight*0.5)
        centerString(string: myName, charSize: myHeight*0.6, center: nameCenter, color: textcolor)
    }

    func centerString(string: String, charSize: CGFloat, center: CGPoint, color: UIColor) {
        // 文字列、文字サイズ、中央を示す座標、
        let stringAttributes: [NSAttributedStringKey : Any] = [
            NSAttributedStringKey.foregroundColor : color,
            NSAttributedStringKey.font : UIFont.systemFont(ofSize: charSize)
        ]
        let nsstr = string as NSString
        // サイズを取得
        let textSize: CGSize = nsstr.size(withAttributes: stringAttributes)
        let textTopLeft = CGPoint(x: center.x - textSize.width/2, y: center.y - textSize.height/2)
        // 文字列の描画
        nsstr.draw(in: CGRect(origin: textTopLeft, size: textSize), withAttributes: stringAttributes)
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if entry {
            entry = false
            if entryCancel != nil {
                entryCancel!()      // Runnerクラスへのアクセス
            }
        } else {
            entry = true
            if entryOk != nil {
                entryOk!();  // Runnerクラスへのアクセス
            }
        }       
        setBackGroundColor()
    }
}
class AddNameButton: UIView {
    
    /**
     * スクロールビューにのせるパーツ
     * タップされた時の動作は、EntryViewに書く
     */
    
    // 氏名入力起動のためのハンドラー
    private var nameInputHandler: (() -> Void)?
    
    override init(frame: CGRect) {
        
        super.init(frame: frame)
        
        backgroundColor = UIColor.white
    
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // ハンドラーのセット
    func setnameInputHandler(handler: @escaping () -> Void) {
        self.nameInputHandler = handler
    }
    
    override func draw(_ rect: CGRect) {
        
        let myWidth = MySettings.Entry.runnerErentryViewWidth()
        let myHeight = MySettings.Entry.runnerErentryViewHeight()
        let lineWidth = myWidth * 0.01
        

        
        // コンテキストの取得
        let context = UIGraphicsGetCurrentContext()
        
        // 両サイド
        context?.setLineWidth(lineWidth)
        context?.move(to: CGPoint(x: lineWidth/2, y: 0))
        context?.addLine(to: CGPoint(x: lineWidth/2, y: myHeight))
        context?.move(to: CGPoint(x: myWidth-lineWidth/2, y: 0))
        context?.addLine(to: CGPoint(x: myWidth-lineWidth/2, y: myHeight))
        context?.strokePath()
        
        // 上下サイド
        context?.setLineWidth(lineWidth*0.3)
        context?.move(to: CGPoint(x: 0, y: lineWidth*0.15))
        context?.addLine(to: CGPoint(x: myWidth, y: lineWidth*0.15))
        context?.move(to: CGPoint(x: 0, y: myHeight - lineWidth*0.15))
        context?.addLine(to: CGPoint(x: myWidth, y: myHeight - lineWidth*0.15))
        context?.strokePath()
        
        // ボタン面
        let nameCenter: CGPoint = CGPoint(x: myWidth * 0.5, y: myHeight*0.5)
        centerString(string: "氏名登録 >>>", charSize: myHeight*0.5, center: nameCenter, color: UIColor.black)
        
    }

(ちょっとと言いながら長いです・・・すみません)

コピペがありありとわかりますよね。

でも、継承を使うとコピペ部分がいらなくなります。

この2つのクラスはUIViewを継承してるんですが、それなら、UIViewを継承した枠線だけを描く中間クラスを作り、そのクラスを継承してやれば、どちらのクラスにも、枠線を描くコードは不要になりますよね?
イメージとしては次のような感じです。

f:id:momonga117:20180610121815p:plain

まあ、できちゃったものをあえて書き換える必要はないんですが、継承の具体的な例として書いてみました。

以前作ったアプリで、6個くらいの縦につながった継承クラスを作りましたが、あれはやりすぎだったかなと反省。

でも、うまく継承を使うと、スッキリするかもです。