AutoLayoutとSwiftで構築するiOS向けSpotifyクローンの作り方
この記事では、Swiftを使ってSpotifyのホーム画面レイアウトをプログラムで再現する方法を解説します。なぜストーリーボードではなくプログラムで構築するのでしょうか?それは、複数のアプローチでUIを構築できるスキルは常に価値があり、コードで実装するスタイルが好きだからです。特にチーム開発やバージョン管理システムを使用している場合、このスキルは大いに役立ちます。
以下はSpotifyモバイルアプリの実際のホーム画面です。このようなレイアウトを実現するためにUICollectionViewを使用し、タブナビゲーションの作成にはTabBarControllerも活用します。
基本要件:まず、Xcode 10以上とSwift 4以上がインストールされていることを確認してください。
プロジェクトのセットアップ
Xcodeで新しいプロジェクトを作成することから始めましょう:
ViewController.swiftで最初に行うべきは、スーパークラスをUIViewControllerからUICollectionViewControllerへ変更することです。今回のクラスはcollectionViewをベースとするためです。
//
// ViewController.swift
// spotifyAutoLayout
//
// Created by admin on 10/31/19.
// Copyright © 2019 Said Hayani. All rights reserved.
//
import UIKit
class ViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = .purple
// Do any additional setup after loading the view.
}
}
この状態でアプリを実行するとビルドに失敗します。AppDelegate.swiftファイル内のdidFinishLaunchingWithOptions関数にあるreturn文の前に、次のコードを追加する必要があります:
let layout = UICollectionViewFlowLayout()
window = UIWindow()
window?.rootViewController = ViewController(collectionViewLayout: layout)
コード全体はこのようになります:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
let layout = UICollectionViewFlowLayout()
window = UIWindow()
window?.rootViewController = ViewController(collectionViewLayout: layout)
return true
}
これでアプリを実行すると、背景色がpurple(紫)に変わっているのが確認できます:
CollectionViewのメソッドを定義する
次のステップは、レイアウトを整理し、セクション間でスペースを均等に分割することです。
CollectionViewのメソッドを定義していきます。手順は以下の通りです:
- 一意の識別子で再利用可能なセルを登録する
- セクション内のアイテム数を定義する
- 登録したセルを使用する
CollectionViewの一部のメソッドを使用するには、UICollectionViewDelegateFlowLayoutに準拠する必要があります。そうすることでメソッドの自動補完も利用できます。それでは、CollectionViewCellの登録から始めましょう。
viewDidLoad()内でcollectionView.register()メソッドを呼び出し、再利用可能なセルを登録します:
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
次に、numberOfItemsInSectionを使ってcollectionView内のセル数を定義します。今はとりあえず5個にしておきましょう:
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
続いて、cellForItemAtで再利用可能なセルを定義します。このメソッドはUICollectionViewCellを返し、cellIdという一意のIDを持ちます。コードは以下のようになります:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
cell.backgroundColor = .red
return cell
}
完全なコードは次のようになります:
import UIKit
class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellId : String = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView.backgroundColor = .purple
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
cell.backgroundColor = .red
return cell
}
}
これで、赤い背景の5つのアイテムが画面に表示されるはずです:
セルにカスタムの幅と高さを設定する
次に、セルを正しい順序で配置し、width(幅)とheight(高さ)を設定します。各セルは画面の幅をそのまま使用します。
幸いsizeForItemAtメソッドがあるため、セルにカスタムの幅と高さを設定できます。このメソッドはCGSize型を返す必要があります:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = view.frame.width
let height = CGFloat(200)
return CGSize(width: width, height: height)
}
ここでは、view.frame.widthを使ってセルに画面の幅を設定し、高さにはCGFloat型のカスタム値を指定しました。
シミュレータで結果を確認してみてください:
ここまでは順調です。次は、再利用可能なカスタムセルを作成しましょう。CustomCellという名前の新しいSwiftファイルを作成します:
CustomCell.swiftは以下のようになります:
import UIKit
class CustomCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
次に、再利用可能なセルをサポートするために2つのメソッド、collectionView.registerとcellForItemAtを修正します。まずregisterメソッドから。UICollectionViewCell.selfをCustomCellに置き換えます:
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
続いて、cellForItemAtをCustomCellにキャストします:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CustomCell
このままでは変化がわかりにくいため、CustomCellに背景色backgroundColor = .yellowを設定しましょう。cellForItemAt内のcell.backgroundColor = .redの行を削除することを忘れずに。背景色が黄色に変われば成功です。
さて、いよいよCustomCellに本格的な機能を加えていきましょう!
Spotifyのホーム画面を見ると、各セクション(この例ではCustomCell)にはセクションタイトルとサブセルが含まれており、横方向にスクロールする仕組みになっています:
セクションタイトルを追加する
セルにタイトルラベルを追加しましょう。CustomCellクラス内にtitleLabel要素を作成します:
let titleLabel: UILabel = {
let lb = UILabel()
lb.text = "Section Title"
lb.font = UIFont.boldSystemFont(ofSize: 14)
lb.font = UIFont.boldSystemFont(ofSize: 14)
return lb
}()
次に、init()ブロック内でこの要素をビューに追加します:
addSubview(titleLabel)
この状態でアプリを実行しても変化は見られません。それは要素にまだ制約を設定していないからです。そこで制約を追加しましょう。titleLabelにlb.translatesAutoresizingMaskIntoConstraints = falseプロパティを追加して、制約を適用できるようにします。
titleLabelをビューに追加した後、制約を定義します:
addSubview(titleLabel)
titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8).isActive = truetitleLabel.leftAnchor.constraint(equalTo: leftAnchor,constant: 8 ).isActive = true
必ず.isActive = trueプロパティを付けてください。これがないと制約が機能しません!
次のステップに進む前に、画面の背景色を黒に変更し、セルの黄色も削除しておきましょう:
サブセルを追加する
さて、ここからが重要なパートです。各セルの中にサブセルを配置していきます。そのために、CustomCellの中にCollectionViewを追加します。
UICollectionViewCellの中にCollectionViewを追加するには、UICollectionViewDelegate、UICollectionViewDelegateFlowLayout、そしてUICollectionViewDataSourceの各プロトコルをCustomCellに準拠させる必要があります。
通常のビューと同じ要領で、collectionView要素を作成しましょう:
let collectionView : UICollectionView = {
// init the layout
let layout = UICollectionViewFlowLayout()
// set the direction to be horizontal
layout.scrollDirection = .horizontal
// the instance of collectionView
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
// Activate constaints
cv.translatesAutoresizingMaskIntoConstraints = false
return cv
}()
最初にviewController.swiftで行ったのと同様に、イニシャライザでlayoutをcollectionViewに渡している点に注目してください。ここではさらに、FlowLayoutのスクロール方向を.horizontalに指定しています。
collectionView要素をサブビューとしてビューに追加しましょう。コードを整理するために、専用の関数を作成します:
fileprivate func setupSubCells(){
// add collectionView to the view
addSubview(collectionView)
collectionView.dataSource = self
collectionView.delegate = self
// setup constrainst
// make it fit all the space of the CustomCell
collectionView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor).isActive = true
collectionView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
collectionView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
}
collectionViewのdelegateとdataSourceを必ずselfに設定してください:
collectionView.dataSource = self
collectionView.delegate = self
その後、initブロック内でこの関数を呼び出します。
この状態でビルドすると、UICollectionViewDelegateとUICollectionViewDelegateFlowLayoutプロトコルに準拠していないため、Xcodeがエラーを表示します。これを解決するには、まずサブセルを再利用可能なセルとして登録する必要があります。
クラスの先頭にcellIdという名前の変数を作成します。セル識別子が必要なときに使用できます:
let cellId : String = "subCellID"
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
エラーを解消するには、あと2つのメソッドが必要です。セクション内のセル数を定義するnumberOfItemsInSectionと、再利用可能なセルを定義するcellForItemAtです。これらはcollectionViewが正常に動作するために不可欠なメソッドです:
// number of cells
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
// reusable Cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
cell.backgroundColor = .yellow
return cell
}
結果はこのようになります:
ご覧のとおり、collectionViewの背景は紫で、サブセルは黄色で表示されています。
最後に、subCellsの高さをセクションの高さに合わせ、幅も同様に設定しましょう。ここでもsizeForItemAtを使用してセルのheightとwidthを定義します。
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = frame.height
let height = frame.height
return CGSize(width: width, height: height)
}
そして完成です!
素晴らしい出来栄えですね!この記事が長くなりすぎないよう、ここで一旦区切ります。第2部では、モック画像を追加して実際のデータを入力していきます。
完全なソースコードはこちら
追加情報、質問、訂正などがあれば、ぜひ下のコメント欄に投稿するか、Twitterでお気軽にご連絡ください。
このチュートリアルの第2部が公開された際に通知を受け取るには、メールリストに購読登録してください。
-
iOS 13でBluetoothマウスをiPhoneに接続する方法【AssistiveTouch活用ガイド】
iOS 13およびiPadOSへのアップデートにより、ついにBluetoothマウスをiPhoneやiPadに接続できるようになりました。アクセシビリティ機能を活用すれば、iPhoneにマウスを接続して、デスクトップPCのように快適にインターネットを閲覧したり、ドキュメントを素早く操作したりすることが可能です。 iPhoneでマウスを使う方法 オフィスでの会議中にプロジェクターと組み合わせてiPhoneを操作したい場合など、マウスがあると非常に便利な場面は多くあります。iOS 13.3ではAssistiveTouch(アシスティブタッチ)機能に変更が加えられ、この優れた機能を使ってiPhone
-
iOS 16搭載iPhoneでバッテリー残量(%)を常に表示する方法
5年前、AppleはiPhone Xを発売しました。画面上部に賛否両論を呼んだ「ノッチ」を備えた、かなり先進的なデザインが特徴でした。見た目のインパクトはさておき、ノッチの導入によりステータスバーに表示できるアイコンや情報のスペースは大幅に狭まりました。その影響の一つとして、Appleはバッテリー残量(%)の常時表示オプションを廃止してしまったのです。画面右上から下にスワイプしてコントロールセンターを開けば、正確なバッテリー残量を確認することはできます(現在も可能です)。しかし、この方法やウィジェットを活用した他の回避策は、すべてのユーザーにとって満足のいくものではありませんでした。嬉しいこと