📱
Netflix風UIをつくってみた
完成図
(焼き魚が大量発生しています、、怠惰)
開発環境
## コード
Modelの定義
Recipe.swift
struct Recipe: Identifiable {
let id: Int
let name: String
let image: String
let description: String
init(id: Int, name: String, image: String, description: String) {
self.id = id
self.name = name
self.image = image
self.description = description
}
}
ViewModelの定義
RecipeViewModel.swift
import SwiftUI
class RecipeViewModel: ObservableObject {
@Published private(set) var categories = [
"定番のレシピ",
"お酒のおつまみ",
"次なるたこパ",
"夏野菜を使ったレシピ"
]
@Published private(set) var teibanRecipes = [
Recipe(id: 1, name: "肉じゃが", image: "nikujaga", description: "これは肉じゃがです"),
Recipe(id: 2, name: "親子丼", image: "oyakodon", description: "これは親子丼です"),
Recipe(id: 3, name: "ハンバーグ", image: "hamberg", description: "これはハンバーグです"),
Recipe(id: 4, name: "照り焼きチキン", image: "teriyaki-chiken", description: "これは親子丼です"),
Recipe(id: 5, name: "カレーライス", image: "carry-rice", description: "これはカレーライスです")
]
@Published var tumamiRecipes = [
Recipe(id: 6, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です"),
Recipe(id: 7, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です"),
Recipe(id: 8, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です"),
Recipe(id: 9, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です"),
Recipe(id: 10, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です")
]
@Published var nextTacopaRecipes = [
Recipe(id: 11, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です"),
Recipe(id: 12, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です"),
Recipe(id: 13, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です"),
Recipe(id: 14, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です"),
Recipe(id: 15, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です")
]
@Published var natsuyasaiRecipes = [
Recipe(id: 16, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です"),
Recipe(id: 17, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です"),
Recipe(id: 18, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です"),
Recipe(id: 19, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です"),
Recipe(id: 20, name: "焼き魚", image: "yakisakana", description: "これは焼き魚です")
]
// カテゴリーに紐づくレシピを表示する
func recipes(for category: String) -> [Recipe] {
switch category {
case categories[0]:
return teibanRecipes
case categories[1]:
return tumamiRecipes
case categories[2]:
return nextTacopaRecipes
case categories[3]:
return natsuyasaiRecipes
default :
return []
}
}
// 配列の中身が5個以上なければ、comming soonと表示する
func shouldShowComingSoon(for category: String) -> Bool {
let shouldShowCommingSoonRecipes = self.recipes(for: category)
return shouldShowCommingSoonRecipes.count < 5
}
func refreshRecipes() {
teibanRecipes.shuffle()
tumamiRecipes.shuffle()
nextTacopaRecipes.shuffle()
natsuyasaiRecipes.shuffle()
}
}
Viewの定義
MainView.swift
import SwiftUI
struct MainView: View {
@StateObject private var viewModel = RecipeViewModel()
var body: some View {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading) {
ForEach(viewModel.categories, id: \.self) { category in
VStack(alignment: .leading) {
Text(category)
.font(.title)
.padding([.leading, .top])
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
if viewModel.shouldShowComingSoon(for: category) {
VStack{
Text("ComingSoon!")
.font(.title3)
}
} else {
ForEach(viewModel.recipes(for: category)) { recipe in
VStack {
NavigationLink(destination: RecipeDetailView(recipe: recipe)) {
Image(recipe.image)
.resizable()
.scaledToFit()
.frame(width: 150, height: 150)
.background(Color.gray)
.cornerRadius(10)
}
Text(recipe.name)
.font(.caption)
}
}
}
}
}
.padding([.leading, .bottom])
}
}
}
}
// 各カテゴリーのレシピを更新する(といっても、順番が変わるだけ)
.refreshable {
viewModel.refreshRecipes()
}
.navigationTitle("レシピ")
}
}
}
#Preview {
MainView()
}
まとめ
Netflixでトークサバイバーを観ようとしていたのですが、
何気なくNetflixのUIを再現してみたいな〜と思い、簡単に再現してみました!
※レシピアプリにしたのはNetflixのように画像で「つくりたい」欲を引き出せたらいいなと思ったためです。
UIをつくったことに満足して、トークサバイバーは全く観ていませんw
SwiftUIを触っていると時間が溶けてしまいます。。たのしい!
PS
Apple Musicも同じようなUIでした。
Discussion