Instagramのホーム画面を作る

Published by @SoNiceInfo at 6/24/2020


Instagramのホーム画面を作っているxcode

ホーム画面を作るための要素

ホーム画面を以下の要素に分解して順番に作り方を紹介します。

タイトルバーとタブ(青枠)

ContentView.swift

ストーリー(オレンジ枠)

StoryView.swift

タイムライン(緑枠)

TimelineView.swift
Instagramのホーム画面を要素に分解した画像

タイトルバーとタブ(青枠) - ContentView.swift

NavigationViewTabViewを組み合わせることでタイトルバーとタブを再現します。
タイトルバーのフォントはSwiftUIでは直接変更できないのでイニシャライザ内でUINavigationBar.appearance()で変更します。

//
//  ContentView.swift
//

import SwiftUI

struct ContentView: View {
    init() {
        UINavigationBar.appearance().titleTextAttributes = [.font : UIFont(name: "Georgia", size: 26)!]
    }

    var body: some View {
        TabView {
            NavigationView {
                Text("Here for Stories and Timelines")
                    // タイトルと左右のアイコンを指定
                    .navigationBarTitle(Text("Instagram"), displayMode: .inline)
                    .navigationBarItems(
                        leading: IconView(systemName: "camera"),
                        trailing: HStack{
                            IconView(systemName: "tv")
                            IconView(systemName: "paperplane")
                            .padding(.leading, 10)
                        }
                        .padding(.bottom, 10)
                    )
                }
                .tabItem {
                    IconView(systemName: "house")
                }
            IconView(systemName: "magnifyingglass")
                .tabItem {
                    IconView(systemName: "magnifyingglass")
                }
            IconView(systemName: "plus.app")
                .tabItem {
                    IconView(systemName: "plus.app")
                }
            IconView(systemName: "heart")
                .tabItem {
                    IconView(systemName: "heart")
                }
            IconView(systemName: "person")
                .tabItem {
                    IconView(systemName: "person")
                }
        }
        // ノッチ部分まで使用
        // Xcode11.4以降は不要
        .edgesIgnoringSafeArea(.top)
        // 選択されているアイコンの色を黒に変更
        .accentColor(.black)
    }
}

// Iconの形式をそろえる
struct IconView: View {
    var systemName: String

    var body: some View {
        Image(systemName: systemName)
            .font(.title)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
ホーム画面のタイトルバーとタブを再現した結果

ストーリー(オレンジ枠) - StoryView.swift

モックはstruct Storyで雛形を作りlet stories: [Story]で定義しています。
ScrollView(.horizonal)で左右にスクロール可能にしています。
ForEachstoriesをひとつずつ順番に表示する処理をしています。
Instagramらしさを高めるためにStoryの枠をグラデーションにしています。

//
//  StoryView.swift
//

import SwiftUI

struct Story {
    let id: Int
    let name: String
    let image: String
}

let stories: [Story] = [
    Story(id: 0, name: "Arupaka", image: "animal_arupaka"),
    Story(id: 1, name: "Buta", image: "animal_buta"),
    Story(id: 2, name: "Hamster", image: "animal_hamster"),
    Story(id: 3, name: "Hiyoko", image: "animal_hiyoko"),
    Story(id: 4, name: "Inu", image: "animal_inu")
]

struct StoryView: View {
    let stories: [Story]

    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack() {
                // 順番にStoryを処理
                ForEach(stories, id: \.id) { (story) in
                    VStack(spacing: 0) {
                        ZStack {
                            Image(story.image)
                                .resizable()
                                .overlay(
                                    // Instagramらしいグラデーション色に!!
                                    Circle().stroke(LinearGradient(gradient: Gradient(colors: [.yellow, .red, .purple]), startPoint: .bottomLeading, endPoint: .topTrailing), lineWidth: 5))
                                .frame(width: 100, height: 100)
                                .clipShape(Circle())
                        }
                        Text(story.name)
                    }
                }
            }
                .padding(.top, 5)
                .padding(.leading, 5)
        }
    }
}


struct StoryView_Previews: PreviewProvider {
    static var previews: some View {
        StoryView(stories: stories)
    }
}
StoryViewを作成した画面

タイムライン(緑枠) - TimelineView.swift

モックはstruct Timelineで雛形を作りlet timelines: [Timeline]で定義しています。
ForEachtimelinesをひとつずつ順番に表示する処理をしています。
画面幅はUIScreen.main.bounds.widthで取得できるので、画像の高さに適用して正方形にしています。
Stackを使って要素を構築します。

//
//  TimelineView.swift
//

import SwiftUI

struct Timeline {
    let id: Int
    let name: String
    let image: String
    let post: String
    let post_image: String
}

let timelines: [Timeline] = [
    Timeline(id: 0, name: "Arupaka", image: "animal_arupaka", post: "This is post content", post_image: "ice_1"),
    Timeline(id: 1, name: "Buta", image: "animal_buta", post: "This is post content", post_image: "ice_2"),
    Timeline(id: 2, name: "Hamster", image: "animal_hamster", post: "This is post content", post_image: "flower"),
    Timeline(id: 3, name: "Hiyoko", image: "animal_hiyoko", post: "This is post content", post_image: "moon"),
    Timeline(id: 4, name: "Inu", image: "animal_inu", post: "This is post content", post_image: "animal_inu")
]

struct TimelineView: View {
    let timelines: [Timeline]

    var body: some View {
        VStack() {
            ForEach(self.timelines, id: \.id) { (timeline) in
                VStack(spacing: 0) {
                    HStack {
                        Image(timeline.image)
                            .resizable()
                            .clipShape(Circle())
                            .overlay(
                                Circle().stroke(Color.white, lineWidth: 4))
                            .frame(width: 50, height: 50, alignment: .leading)
                        Text(timeline.name)
                            .fontWeight(.bold)
                        Spacer()
                        Image(systemName: "list.bullet")
                    }
                        .padding(.horizontal, 5)
                    Divider()
                    Image(timeline.post_image)
                        .resizable()
                        .scaledToFill()
                        .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width, alignment: .center)
                        .clipShape(Rectangle())
                    Divider()
                    Group {
                        Text("(timeline.name) ").fontWeight(.bold) +
                        Text(timeline.post)
                    }
                        .padding(.horizontal, 5)
                        .frame(width: UIScreen.main.bounds.width, alignment: .leading)
                }
            }
        }
    }
}

struct TimelineView_Previews: PreviewProvider {
    static var previews: some View {
        TimelineView(timelines: timelines)
    }
}
TimelineViewを作成した画面

組み合わせる - ContentView.swift

上記で作成したStoryViewTimelineViewVStackで表示します。

//
//  ContentView.swift
//

import SwiftUI

struct ContentView: View {
    init() {
        UINavigationBar.appearance().titleTextAttributes = [.font : UIFont(name: "Georgia", size: 26)!]
    }

    var body: some View {
        TabView {
            NavigationView {
                // 縦スクロール可能にする
                ScrollView(.vertical, showsIndicators: false) {
                    // VStackで作成したViewを構築
                    VStack {
                        StoryView(stories: stories)
                        Divider()
                        TimelineView(timelines: timelines)
                    }
                }
                    .navigationBarTitle(Text("Instagram"), displayMode: .inline)
                    .navigationBarItems(
                        leading: IconView(systemName: "camera"),
                        trailing: HStack{
                            IconView(systemName: "tv")
                            IconView(systemName: "paperplane")
                            .padding(.leading, 10)
                        }
                            .padding(.bottom, 10)
                    )
                }
                .tabItem {
                    IconView(systemName: "house")
                }
            IconView(systemName: "magnifyingglass")
                .tabItem {
                    IconView(systemName: "magnifyingglass")
                }
            IconView(systemName: "plus.app")
                .tabItem {
                    IconView(systemName: "plus.app")
                }
            IconView(systemName: "heart")
                .tabItem {
                    IconView(systemName: "heart")
                }
            IconView(systemName: "person")
                .tabItem {
                    IconView(systemName: "person")
                }
        }
        // Xcode11.4以降は不要
        .edgesIgnoringSafeArea(.top)
        .accentColor(.black)
    }
}

struct IconView: View {
    var systemName: String

    var body: some View {
        Image(systemName: systemName)
            .font(.title)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

変更履歴

[2020/03/30] Xcode 11.4からTabViewNavigationViewの組み合わせで.edgesIgnoringSafeAreaは不要となりました。

参考

navigationbar - How can the background or the color in the navigation bar be changed? - Stack Overflow
How to render a gradient - a free SwiftUI by Example tutorial

素材

かわいいフリー素材集 いらすとや
Beautiful Free Images & Pictures | Unsplash

    アプリをリリースしました!

    ToDoアプリ

    iCloudを利用したデバイス間データ共有、ダークモードに対応しています。

    リリースしたToDoアプリのスクリーンショット

    IPアドレス履歴保存アプリ

    取得したIPアドレスを確認、位置情報とともに保存できます。

    リリースしたIPアドレス保存アプリのスクリーンショット