📱

Netflix風UIをつくってみた

2024/09/20に公開

完成図

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でした。

GitHubで編集を提案

Discussion