はじめに
WWDC 2019 で紹介された UICollectionView
でシュバババと並べ替えが起きるアニメーションをみてやってみたい!と思った人は多いと思います(Advances in UI Data Sourcesの 22:00 ~ くらいのやつ)。
ドット絵表示して切り替えるときに使うとかっこいいんじゃないかと思いこういうのを作ってみました。
CollectionViewでドット絵🤪 pic.twitter.com/jtR5o1oSTF
— am10 (@am103141592) August 20, 2022
ドット絵表示
まず UICollectionView
を使って 32 * 32 のキャンバスを作ってそこにドット絵を表示します。
Compositional Layouts と Diffable Data Sources どちらもあまり理解してないので下記記事参考にさせていただきました。
- Compositional LayoutとDiffable Data Sourceを使ってiOSアプリのつくれぽ詳細画面を実装する
- Diffable Data Sourceでセルをリロードする方法
- 【Swift】UICollectionViewCompositionalLayoutを使って数種類のカスタムレイアウトを実装してみる
- 時代の変化に応じて進化するCollectionView ~Compositional LayoutsとDiffable Data Sources~
キャンバス作成
下記のように 32 * 32 のキャンバスを作成します(ドット打つの大変だったので 16 * 16 のがいいかもです)。
private enum Section {
case main
}
/// サイズは256*256にしておく
@IBOutlet private weak var collectionView: UICollectionView!
private var dataSource: UICollectionViewDiffableDataSource<Section, Int>!
private func makeSection() -> NSCollectionLayoutSection {
let itemCount = 32 // 横に並べる数
let itemSpacing: CGFloat = 0 // セル間のスペース
let dotSize: CGFloat = 8 // ドットのサイズ
let item = NSCollectionLayoutItem(layoutSize: .init(widthDimension: .absolute(dotSize),
heightDimension: .absolute(dotSize)))
let items = NSCollectionLayoutGroup.horizontal(layoutSize: .init(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0)),
subitem: item,
count: itemCount)
items.interItemSpacing = .fixed(itemSpacing)
let group = NSCollectionLayoutGroup.vertical(layoutSize: .init(widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(dotSize)),
subitems: [items])
let section = NSCollectionLayoutSection(group: group)
return section
}
collectionView.collectionViewLayout = UICollectionViewCompositionalLayout(section: makeSection())
dataSource = UICollectionViewDiffableDataSource<Section, Int>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, item: Int) -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
cell.backgroundColor = .systemGreen
return cell
}
let items = (0...1023).map { Int($0) }
var snapshot = NSDiffableDataSourceSnapshot<Section, Int>()
snapshot.appendSections([.main])
snapshot.appendItems(items, toSection: .main)
dataSource.apply(snapshot, animatingDifferences: false)
こんな感じです。
ドット絵表示
使う色の文だけ Dot
を定義します。
enum Dot {
case chocolate1
case chocolate2
case cracker1
case cracker2
case outline
case background
var color: UIColor {
switch self {
case .chocolate1:
return .init(red: 93/255, green: 63/255, blue: 48/255, alpha: 1.0)
case .chocolate2:
return .init(red: 60/255, green: 42/255, blue: 33/255, alpha: 1.0)
case .cracker1:
return .init(red: 236/255, green: 193/255, blue: 131/255, alpha: 1.0)
case .cracker2:
return .init(red: 164/255, green: 137/255, blue: 97/255, alpha: 1.0)
case .outline:
return .init(red: 96/255, green: 96/255, blue: 96/255, alpha: 1.0)
case .background:
return .systemGreen
}
}
}
こんな感じで 1024 の配列でドット絵を定義します。
let mushroom: [Dot] = [
.background, .background, .background, .background, .background, .background, .background, .background,
.background, .background, .background, .background, .outline, .outline, .outline, .outline,
.outline, .outline, .outline, .outline, .background, .background, .background, .background,
.background, .background, .background, .background, .background, .background, .background, .background,
:
:
:
]
あとは下記のようにしてドット絵を表示するだけです!
private var dataList: [Dot] = mushroom
dataSource = UICollectionViewDiffableDataSource<Section, Int>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, item: Int) -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
cell.backgroundColor = self.dataList[item].color
return cell
}
こんな感じです。
ドット絵切り替え
ドット絵が表示できたのであとは複数のドット絵の切り替え処理です。
もう1つドット絵データを作成します。
let bambooshoot: [Dot] = [
.background, .background, .background, .background, .background, .background, .background, .background,
.background, .background, .background, .background, .background, .background, .background, .outline,
.outline, .background, .background, .background, .background, .background, .background, .background,
.background, .background, .background, .background, .background, .background, .background, .background,
:
:
:
]
切り替え用フラグを用意してデータ生成の処理を修正します。
private var mode = 0
let items = (0...mushroom.count-1).map { Int($0) }
dataList = .init(repeating: .background, count: items.count)
items.forEach { item in
switch mode {
case 1:
dataList[item] = bambooshoot[item]
case 2:
dataList[item] = mushroom[item]
default:
dataList[item] = .background
}
}
あとはボタン押下時に更新処理を実行するだけです!
@IBAction func change(_ sender: Any) {
mode += 1
if mode > 2 {
mode = 0
}
var snapshot = dataSource.snapshot()
let allItems = snapshot.itemIdentifiers
let items = allItems.shuffled()
snapshot.deleteItems(allItems)
snapshot.appendItems(items, toSection: .main)
snapshot.reloadItems(items)
items.enumerated().forEach { item in
switch mode {
case 1:
dataList[item.element] = bambooshoot[item.offset]
case 2:
dataList[item.element] = mushroom[item.offset]
default:
dataList[item.element] = .background
}
}
dataSource.apply(snapshot, animatingDifferences: true)
}
おまけ
SpriteKit の SKEmitterNode(爆炎部分)と組み合わせてこんなこともできます
(再生するたけのこ)
爆散と再生処理はこんな感じです。
/// 爆散処理
func shuffleDot() {
var snapshot = dataSource.snapshot()
let items = snapshot.itemIdentifiers
snapshot.deleteItems(items)
snapshot.appendItems(items.shuffled(), toSection: .main)
dataSource.apply(snapshot, animatingDifferences: true)
}
/// 再生処理
func resetDot() {
var snapshot = dataSource.snapshot()
let items = snapshot.itemIdentifiers
snapshot.deleteItems(items)
snapshot.appendItems(items.sorted(by: { $1 > $0 }), toSection: .main)
dataSource.apply(snapshot, animatingDifferences: true)
}
おわりに
みんなも UICollectionView
を使ってドット絵をシュバババと表示しよう