第21回 swiftでスクロールビュー
スクロールビューって、利用しない手はないのに、なかなかいいサンプルにお目にかかりません。ぜひぜひ参考にしてください。結構、教科書的に書けたように思います。
プログラムは3つの部分に分かれます。
A:スクロールビューを呼び出すプログラム
B:スクロールビュー本体
C:スクロールビューに配置するオブジェクト
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の数値を変えると、見栄えがすごく変わってしまいます。
完成した画面がこちら。
スクロールしているところはお見せできませんが、実機でやってみてもツルンとスクロールしてくれて、気持ちいいですよ。
ただ、ここまでは、そんなに大変じゃないんですが、スクロールビューをタッチして、選択したり、名前登録に入ったり、まだまだ作り込まないといけません。そして、こういうところが、結構情報不足だったりするんですよね。
多分、このアプリで一番大変なのはこの辺りではないかと思っています。
ご参考になれば、幸いです。