古きCover Flowを訪ねて
はじめに
こんにちは。株式会社mikanでiOSエンジニアをしている@sauna1137です。 これは mikan Advent Calendar 2024 10日目の記事です🧖
9日目の記事はOasis好き@uk_oasisさんの「趣味でキーボードを作っていたら仕事になった話」でした。 mikan iOSの新機能「タイピング学習」の制作秘話についてぜひ読んでみてください! 待ち望まれていた学習機能なのでアプリもお試しください!
自己紹介
改めて、2024年10月からiOSエンジニアとしてmikanに入社したsoneと申します。工作機械のハードウェアエンジニアとして5年ほど勤め、その後2年半ほど都内のスタートアップでiOS等の開発業務に携わり、現在に至ります。よければ入社エントリもお読みいただけたら嬉しく思います。ukさんの記事にもあった通り、先日のOasisのライブチケットの抽選・一般販売共に撃沈し悔しい思いをしました… 本当に悔しいです!
Cover Flowとは?
2006年に導入されたアルバムのジャケット等をスクロールで高速に閲覧できる機能・UIです。アルバムアートをスワイプして切り替える感覚は斬新で、当時中学生だった私はiPod touchでスクロールしては楽しんでいました。しかし、あまり使われなかったのかiTunesやiOSから姿を消していきました。あの体験をもう一度味わいたい!とSwiftUIを使って「Cover Flow」を再現してみます。古き良き? 機能を再現して、学びを得たいと思います。温故知新です。
早速実装してみます!
まずはScrollViewで横並びのアートワークの枠を作成します。適当な正方形のコンテナを用意します。
struct ContentView: View {
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 20) {
ForEach(1...7, id: \.self) { i in
Rectangle()
.fill(Color.white)
.frame(width: 160, height: 160)
}
}
.padding()
}
}
}
}
続いてアートワークの反射用にreflectionメソッドを追加します。アートワークの下に反射効果を追加するためのメソッドです。Rectangle()に適用させると反射されたViewが反映されてCoverFlow感がでてきました!
extension View {
func reflection() -> some View {
self.overlay(
GeometryReader { proxy in
self
.scaleEffect(y: -1)
.mask(
LinearGradient(
gradient: Gradient(colors: [
.white.opacity(0.5),
.white.opacity(0.4),
.white.opacity(0.3),
.white.opacity(0.2),
.white.opacity(0.1)
] + Array(repeating: .clear, count: 4)),
startPoint: .top,
endPoint: .bottom
)
)
.offset(y: proxy.size.height + 1)
}
)
}
}
スナッピングできるように.scrollTargetLayoutや .scrollTargetBehavior(.viewAligned)を追加します。こちらはiOS17から使用できます。また両サイドのアートワークが傾くようにrotationエフェクトを追加します。
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 20) {
ForEach(1...7, id: \.self) { i in
GeometryReader { itemGeometry in
let itemMidX = itemGeometry.frame(in: .global).midX
let distanceFromCenter = abs(itemMidX - screenMidX)
Rectangle()
.fill(Color.white)
.frame(width: 160, height: 160)
.reflection()
.rotation3DEffect(
.degrees(calcRotation(itemGeometry)),
axis: (x: 0, y: 1, z: 0),
anchor: .center
)
}
.frame(width: 160, height: 160)
}
}
.padding(.horizontal, (size.width - 160) / 2)
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.scrollIndicators(.hidden)
.scrollClipDisabled()
最後にアニメーションとアートワークを追加します!Cover Flowができあがりました!まだ改善の余地はありますが眺めているだけで楽しいですね〜
完成したプロジェクトはこちらです。
まとめ
今回 Cover Flowを再現することで、改めて「触っているだけで楽しい、直感的なUI」の魅力を実感しました。そして作り込まれたCover Flowの再現するのは難しく、完全ではないのでまた機会があれば挑戦してみようと思います。mikanでは「プロダクトバリュー」という考え方があり、「mikanらしさ」を意識しながら開発しています。これからも、「触り心地の良さ」「楽しさ」「直感的な操作感」を追求して、ユーザ体験を向上させるプロダクトを作っていきたいと思います!
最後に
最後までお付き合いいただきありがとうございます!mikanでは様々な職種で積極採用しています!お声がけいただければ気軽に面談できますので、いつでもご連絡ください!この季節はこたつでみかんを食べながらカジュアルにお話できたら嬉しいです!お待ちしております🍊
明日のアドベントカレンダー担当は@maruさんです!どんな内容か楽しみですね!