フリープログラマー日記

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

第21回 swiftでスクロールビュー

 スクロールビューって、利用しない手はないのに、なかなかいいサンプルにお目にかかりません。ぜひぜひ参考にしてください。結構、教科書的に書けたように思います。

プログラムは3つの部分に分かれます。
A:スクロールビューを呼び出すプログラム
B:スクロールビュー本体
C:スクロールビューに配置するオブジェクト

f:id:momonga117:20180518101018p:plain

xcodeの階層表示ですが、クリーム色の小さな長方形がCです。

色々付け加えてますが、まず、CのオブジェクトをRunnerクラスとして定義しました。

import UIKit

class Runner: UIView {
    
    let thiswidth: CGFloat
    let thisheight: CGFloat
    
    let lineWidth: CGFloat
    let myNumber: Int
    
    var name: String = "未登録"        //氏名表示
    var entry: Bool = false         // エントリーしたか?
    
    
    init(frame: CGRect, number: Int) {
        // 自前のイニシャライザ
        thiswidth = frame.width
        thisheight = frame.height
        
        lineWidth = frame.width*0.01
        myNumber = number
        super.init(frame: frame)
        // 背景色をセット、エントリー時には色を変える 色は未調整
        if entry {
            backgroundColor = UIColor(red: 255/255, green: 255/255, blue: 240/255, alpha: 255/255)
        } else {
            backgroundColor = UIColor(red: 255/255, green: 255/255, blue: 180/255, alpha: 255/255)
        }
        
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func draw(_ rect: CGRect) {
        // いつものビューへの描画

        // 背景色をセット、エントリー時には色を変える 色は未調整
        if entry {
            backgroundColor = UIColor(red: 255/255, green: 255/255, blue: 240/255, alpha: 255/255)
        } else {
            backgroundColor = UIColor(red: 255/255, green: 255/255, blue: 180/255, alpha: 255/255)
        }

        // 番号枠,氏名枠の幅計算
        let numberWidth = thiswidth * 0.22
        let nameWidth = thiswidth - 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: thisheight))
        context?.move(to: CGPoint(x: thiswidth-lineWidth/2, y: 0))
        context?.addLine(to: CGPoint(x: thiswidth-lineWidth/2, y: thisheight))
        context?.strokePath()
        
        // 上下サイド
        context?.setLineWidth(lineWidth*0.3)
        context?.move(to: CGPoint(x: 0, y: lineWidth*0.15))
        context?.addLine(to: CGPoint(x: thiswidth, y: lineWidth*0.15))
        context?.move(to: CGPoint(x: 0, y: thisheight - lineWidth*0.15))
        context?.addLine(to: CGPoint(x: thiswidth, y: thisheight - 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: thiswidth, y: lineWidth*0.4))
        }
        
        // 中間の仕切り線
        context?.setLineWidth(lineWidth*0.6)
        context?.move(to: CGPoint(x: numberWidth, y: 0))
        context?.addLine(to: CGPoint(x: numberWidth, y: thisheight))
        context?.strokePath()
        
        
        // 番号の描画
        let textcolor = name=="未登録" ? UIColor.lightGray : UIColor.black
        let numCenter: CGPoint = CGPoint(x:numberWidth*0.5, y: thisheight*0.5)
        centerString(string: String(myNumber), charSize: thisheight*0.5, center: numCenter, color: textcolor)
        
        // 氏名の描画
        let nameCenter: CGPoint = CGPoint(x: numberWidth + lineWidth + nameWidth * 0.5, y: thisheight*0.5)
        centerString(string: name, charSize: thisheight*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)
    }
}

体裁にこだわったため、少し長くなりました。
でも、これができたら、あとは楽です。

スクロールビューは継承して作っています。

import UIKit

class RunnerList: UIScrollView {
    
    private var aWidth: CGFloat
    private var aHeight: CGFloat
    
    
    // このクラスには選手名のリストが必要
    
    // 暫定的に50人いるとして、ビューを作成
    
    var member: Int = 50
    
    override init(frame: CGRect) {
        // 一人分のサイズをもらっておく
        aWidth = 0.0
        aHeight = 0.0
        
        
        super.init(frame: frame)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    init(frame: CGRect, unitWidth: CGFloat, unitHeight: CGFloat) {
        // 自前のイニシャライザ
        // 一人分のサイズをもらっておく
        aWidth = unitWidth
        aHeight = unitHeight
        super.init(frame: frame)
        
        // ここで走者リストの確認(後で実装)
        // メンバー数だけRunner(UIView)を作成
        // 初期状態では50番まで氏名がなくとも枠を作成する。
        
        self.contentSize = CGSize(width: unitWidth, height: unitHeight*50)
        
        
        for i: Int in 1...50 {
            
            let myRect = CGRect(x: 0.0, y: unitHeight * CGFloat(i-1), width: unitWidth, height: unitHeight)
            let unit: Runner = Runner(frame: myRect, number: i)
            addSubview(unit)
        }
    }
}

50個のRunnerオブジェクトを乗せているのがわかると思います。でも、大事なのは、self.contentSizeのところで、ここで、スクロールするかどうか決まります。指定するのを忘れないでください。

なお、一人分のサイズは呼び出し側よりもらっています。

呼び出し側はこんな感じです。

        let listFrame: CGRect = CGRect(x: myWidth * 0.1, y: myWidth * 0.2, width: myWidth * 0.8, height: myHeight - myWidth * 0.4)
        
        let list: RunnerList = RunnerList(frame: listFrame, unitWidth: myWidth * 0.8, unitHeight: myWidth * 0.13)
        
        addSubview(list)

単純ですが、frameの数値を変えたり、unitWidth,unitheightの数値を変えると、見栄えがすごく変わってしまいます。


完成した画面がこちら。

f:id:momonga117:20180518103409p:plain

スクロールしているところはお見せできませんが、実機でやってみてもツルンとスクロールしてくれて、気持ちいいですよ。

ただ、ここまでは、そんなに大変じゃないんですが、スクロールビューをタッチして、選択したり、名前登録に入ったり、まだまだ作り込まないといけません。そして、こういうところが、結構情報不足だったりするんですよね。

多分、このアプリで一番大変なのはこの辺りではないかと思っています。

ご参考になれば、幸いです。