はじめに
最近、デザイン実装のレベルを上げるために、Dribbbleで見つけたコンセプトデザインの模写をしています。
今回は以下のデザインの実装について紹介したいと思います。
画面をスクロールをすると、スクロールに合わせて水温を調整することができるホームコントロールアプリのコンセプトデザインです。
実際に作ったもの
数字のアニメーションや波模様の動きなどを忠実には実装できていませんが、だいたいの実装はできました。
コードは公開しているので、よければ見てみてください。
https://github.com/EndouMari/ConceptDesign-HomeControlApp
構成
このデザインの構成は主に3つです。
- スクロールに合わせて変化する水温
- 水温に合わせて変化する背景
- 波模様
実装について
今回は「スクロールに合わせて変化する水温」の表示の実装について説明します。
全体の説明については公開しているコードを見てください。
水温に合わせて変化する背景についてはpotatotipsで発表した資料があるので、こちらを見てください。
https://speakerdeck.com/endoumari/potatotips-56-concept-design
今回の実装では、水温は0~60の範囲とします。
スクロールに合わせて変化する水温を実装するために、以下2つについて考えました。
- スクロールに応じての水温の計算
- 水温の数値表示
それぞれどう実装したかを紹介します。
スクロールに応じての水温の計算
画面のスクロールに応じて水温を変更するために、指をおいた地点から動かした地点までの移動距離を求めます。
水温を求めるのに、画面全体の何割スクロールしているかを元に計算するので、スクロールするたびに移動距離を求めます。
今回計算をするためにtouchesMoved(_:with:)
を使用しました。
var currentPointY: CGFloat = 0.0 // どのくらいスクロールしているかの数値
let maxWaterTemperature: Int = 60
class ViewController: UIViewController {
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let diff = touch.previousLocation(in: view).y - touch.location(in: view).y
guard diff != 0.0 else { return }
currentPointY = diff
let value: Int = Int(currentPointY / view.frame.height * CGFloat(maxWaterTemperature))
}
}
これで、水温を求めることができました。
次はこの求めた水温をどうやってコンセプトデザインのように表示するかについてです。
水温の数値表示
コンセプトデザインをよく観察してみると、15から49に数値が変わるのに一の位の動きが5から9にしか変化していません。
これを実装するには、あらかじめ設定する水温を先に決めないと実装するのは難しいです。
しかし、コンセプトデザイン上では指の動きと同じタイミングで変化をしています。
これと全く同じ動きを実装するのは難しいので、一の位の動きは妥協して数値が変化するたびに反映するようにしました。
水温を表示するのに、一の位を表示するためのUICollectionView、十の位を表示するためのUICollectionView、2つを使用して実装しました。
数値をcollectionViewに渡すとscrollToItem(at:at:animated:)
を使用して数値の表示変更をします。
また、0から9、9から0のようにくらいが変わるときにきれいに表示できるように要素数を3倍持つようにしています。
今回はUICollectionViewを継承したNumberCollectionを実装しました。
class NumberCollectionview: UICollectionView {
var numberData: [Int] = []
var currentNumberIndex: Int = 0
private var currentSection: Int = 0
enum Direction {
case up
case down
}
func setup(numberData: [Int], index: Int) {
self.numberData = numberData
scrollToItem(at: .init(item: index, section: 1), at: .top, animated: false)
}
func scrollToItem(at itemIndex: Int, direction: Direction) {
let indexPath: IndexPath
if direction == .up && itemIndex <= currentNumberIndex {
indexPath = .init(item: itemIndex, section: 2)
} else if direction == .down && itemIndex >= currentNumberIndex {
indexPath = .init(item: itemIndex, section: 0)
} else {
indexPath = .init(item: itemIndex, section: 1)
}
scrollToItem(at: indexPath, at: .top, animated: true)
currentNumberIndex = itemIndex
currentSection = indexPath.section
}
}
extension NumberCollectionview: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numberData.count
}
...
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
if currentSection != 1 {
scrollToItem(at: .init(item: currentNumberIndex, section: 1), at: .top, animated: false)
}
}
}
NumberCollectionViewに一の位、十の位の数値を設定して数値の表示をします。
var currentPointY: CGFloat = 0.0 // どのくらいスクロールしているかの数値
let maxWaterTemperature: Int = 60
@IBOutlet weak var numberCollectionView1: NumberCollectionview!
@IBOutlet weak var numberCollectionView2: NumberCollectionview!
class ViewController: UIViewController {
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let diff = touch.previousLocation(in: view).y - touch.location(in: view).y
guard diff != 0.0 else { return }
currentPointY = diff
let value: Int = Int(currentPointY / view.frame.height * CGFloat(maxWaterTemperature))
let onesDisit = value % 10
if numberCollectionView2.currentNumberIndex != onesDisit {
let direction: NumberCollectionview.Direction = diff > 0 ? .up : .down
numberCollectionView2.scrollToItem(at: onesDisit, direction: direction)
}
let tensDisit = value / 10
if numberCollectionView1.currentNumberIndex != tensDisit {
let direction: NumberCollectionview.Direction = diff > 0 ? .up : .down
self.numberCollectionView1.scrollToItem(at: tensDisit, direction: direction)
}
}
}
以上の実装でスクロールに応じての水温を表示することができます。
詳しくは公開されているコードを見てください!
まとめ
コンセプトデザインを実装してみて、普段の業務では使わない内容を学ぶことができてよかったです。
最初はどう実装するか悩みましたが、デザインを観察して分解してみることで何が必要かがわかり実装することができました。
デザインの実装の幅を広めるためにも、これからもコンセプトデザインの実装を続けていこうと思います。