システム開発<基本編>SwiftUIでベーシックなTabViewと画像を回転させるアプリケーション

Swift

前回は、SwiftUIで、回転木馬(メリーゴーランド)のように画像を自動でスライド・循環する

カルーセルアプリの開発記事を紹介しました。

 

 

今回は、SwiftUIで、ベーシックなTabViewと画像を回転させるアプリケーションを開発する

記事を紹介しようと思います。

SwiftUIでベーシックなTabViewと画像を回転させるアプリケーション

 

 

習得内容

今回、SwiftUIで、ベーシックなTabViewと画像を回転させるアプリケーションを作成

する事で、下記の内容を習得する事が出来ます。

  • TabView:子Veiw が6つ以上になると、More というリストの Tab になり、5つ目以降のTab が格納されます。
  • NavigationStack:3種類の画面遷移から、配列コントロールした遷移を実現します。
  • SwiftUI View名称の登録エラー:Vew名称を正しく登録して、無駄なエラーを減らしましょう。
  • RotateGesture:画像を任意に回転させる事が出来ます。
  • TapGesture(count: 3):3回押した(トリプルタップした)時のアクションを実行します。
  • Liquid Glass:iOS26.0以降から採用された、ガラスっぽい見た目?の Tab になります。

 

 

動作環境

今回も、アプリが動作する環境は下記になります。

  • mac OS:Sequoia 15.6.1
  • iOS:26.0
  • Xcode:26.0.1
  • Swift:6.2.1

 

 

NavigationStack

NavigationStack については、過去の記事で詳しく説明しましたので、今回も、

詳細説明を省略させて頂きます。

 

NavigationStack → TabView → SelectTabView

NavigationStack で、配列コントロールを実現する為に、View を作成して行きます。

  1. TabView:ベーシックな TabView で、画像を回転させたり、3回押した(トリプルタップした)時のアクションを実現します。
  2. SelectTabView:子Veiw が6つ以上になると、More というリストの Tab になり、5つ目以降の Tab が格納されます。

 

Xcode の上部タブメニューより、File → New → File from Template を選択します。

 

SwiftUI View を選択します。

 

名称は、TabVeiw を入力してみます。

 

Create ボタンを押して、TabVeiw.swift を作成します。

 

次に、SelectTabVeiw を作成する為、再度、Xcode の上部タブメニューより、

File → New → File from Template を選択します。

 

SwiftUI View を選択します。

 

名称は、SelectTabVeiw を入力してみます。

 

Create ボタンを押して、SelectTabVeiw.swift を作成します。

 

SwiftUI View名称の登録エラー

TabVeiw.swift と SelectTabVeiw を作成して処理の実装を進めて行きますが、今回、

致命的な間違いをしていました。

処理の実装を進めて行き、問題無いかどうか確認の為、アプリの実行を試してみました。

Xcode の上部タブメニューより、Product → Run を選択したところ、下記のような

コンパイルエラーとなりました。

 

 

う、う、動かない…

TabVeiw.swift を登録した途端に動かなくなり、色々と調査を行い、Xcode の変更や

コンポーネントの変更が有ったのか?とか、良からぬ事を考えながら、丸半日の時間を

原因調査に費やして(無駄にして?)しまっていました。

 

実は、上記に記載したように、【TabVeiw.swift を登録した】ことが原因でした。

TabVeiw 自体が標準コンポーネントなので、SwiftUI View の登録で、同名称で登録すると

動かなくなるのは当然で、アホですよね。

 

同じような事は過去にも有り、Python で wordcloud の実装でも、やらかしてしまいました。

ライブラリの wordcloud をインポートしているにも関わらず、wordcloud と同名称で

登録して動かなくなりました。

wordcloud の記事には記載しませんでしたが、無駄な時間を費やさないように、

気を付けましょう。

 

TabVeiw から BaseTabView へ修正

登録した TabVeiw.swift を削除して、新たに別名で作成し直しても良いのですが、

せっかくなので、ソースを直接、修正します。

 

 

上記のファイル名と、Struct の右側、#Preview の3箇所を、TabVeiw から BaseTabVeiw

に修正します。

 

 

BaseTabVeiw.swift に修正しましたので、処理の実装を進めます。

NavigationListVeiw のソースも、TabVeiw から BaseTabVeiw に修正する必要が

有りますので、下部の NavigationListVeiw のソースで説明します。

 

ContentView:メインメニュー(ランチャー)

メインメニュー(ランチャー):ContentView を確認します。

 


import SwiftUI

struct ContentView: View {

    @State var isPopupView_1 = false
    @State var isPopupView_2 = false
    @State var isPopupView_3 = false
    @State var isPopupView_4 = false
    @State var isPopupView_5 = false

    var body: some View {
        
        VStack{
            Button {
                self.isPopupView_1.toggle()
            } label: {
                Text("QRCodeView")
                    .font(.title2)
            }
            .fullScreenCover(isPresented: $isPopupView_1, content: {
                QRCodeView(isPopupView_1: $isPopupView_1)
            })
            .padding()

            Button {
                self.isPopupView_2.toggle()
            } label: {
                Text("ColorPickerView")
                    .font(.title2)
            }
            .sheet(isPresented: $isPopupView_2, content: {
                ColorPickerView(isPopupView_2: $isPopupView_2)
            })
            .padding()

            Button {
                self.isPopupView_3.toggle()
            } label: {
                Text("ColorSliderRGBView")
                    .font(.title2)
            }
            .sheet(isPresented: $isPopupView_3, content: {
                ColorSliderRGBView(isPopupView_3: $isPopupView_3)
            })
            .padding()

            Button {
                self.isPopupView_4.toggle()
                
            } label: {
                Text("ColorSliderHSBView")
                    .font(.title2)
            }
            .sheet(isPresented: $isPopupView_4, content: {
                ColorSliderHSBView(isPopupView_4: $isPopupView_4)
            })
            .padding()

            Button {
                self.isPopupView_5.toggle()
                
            } label: {
                Text("NavigationListView")
                    .font(.title2)
            }
            .sheet(isPresented: $isPopupView_5, content: {
                NavigationListView(isPopupView_5: $isPopupView_5)
            })
            .padding()
        }
    }
}

#Preview {
    ContentView()
}

 

メインメニュー(ランチャー)は、以下の内容です。

  • QRCodeView
  • ColorPickerView
  • ColorSliderRGBView
  • ColorSliderHSBView
  • NavigationListView

今回も、メインメニュー(ランチャー):ContentView に変更は有りませんので、

このまま利用して行きます。

 

NavigationListView:配列コントロール

NavigationListVeiw のソースに、追記・修正をして行きます。

 

ソース例:NavigationListVeiw(配列コントロール)

前回の記事で、CarouselView(path: $path) を追加した NavigationListVeiw のソースを

確認してみます。

 


import SwiftUI

struct NavigationListView: View {

    @Binding var isPopupView_5: Bool
    @State var path: [String]=[]

    var navi_lists = [
        "SwipeView",
        "CarouselView",
        "TabView",
        "TabBarView"
    ]

    var body: some View {
        VStack {

            NavigationStack(path: $path) {

                List(navi_lists, id: \.self) { navi_list in

                    NavigationLink(navi_list, value: navi_list)
                        .font(.system(size: 22))
                }
                .navigationDestination(for: String.self) { navi_list in

                    switch (navi_list) {

                    case "SwipeView":
                        SwipeView(path: $path)
                            .navigationTitle(navi_list)
                            .navigationBarTitleDisplayMode(.inline)

                    case "SwipeDetailView":
                        SwipeDetailView(path: $path)
                            .navigationTitle(navi_list)
                            .navigationBarTitleDisplayMode(.inline)

                    case "CarouselView":
                        CarouselView(path: $path)
                            .navigationTitle(navi_list)
                            .navigationBarTitleDisplayMode(.inline)

                    case "TabView":
                        Text(navi_list)
                            .font(.system(size: 22))
                            .foregroundColor(.green)
                            .padding()

                    case "TabBarView":
                        Text(navi_list)
                            .font(.system(size: 22))
                            .foregroundColor(.orange)
                            .padding()

                    default:
                        Text(navi_list)
                            .font(.system(size: 22))
                            .foregroundColor(.blue)
                            .padding()
                    }
                }
                .navigationTitle("NavigationList")
                .font(.system(size: 22))
                .foregroundColor(.purple)
            }
        }

        Button(action: {
            withAnimation {
                self.isPopupView_5.toggle()
            }
        }, label: {
            Text("Close")
                .font(.title2)
                .foregroundColor(.black)
        })
        .padding()
    }

}

#Preview {
    NavigationListView(isPopupView_5: .constant(false))
}

 

ソース例:NavigationListVeiw(配列コントロール:追加・修正版)

NavigationListVeiw のソースに、追記・修正をしたソースを紹介します。

 


import SwiftUI

struct NavigationListView: View {

    @Binding var isPopupView_5: Bool
    @State var path: [String] = []

    var navi_lists: [String] = [
        "SwipeView",
        "CarouselView",
        "BaseTabView",
        "TabBarView"
    ]

    var body: some View {
        VStack {

            NavigationStack(path: $path) {

                List(navi_lists, id: \.self) { navi_list in

                    NavigationLink(navi_list, value: navi_list)
                        .font(.system(size: 22))

                }
                .navigationDestination(for: String.self) { navi_list in

                    switch (navi_list) {

                    case "SwipeView":
                        SwipeView(path: $path)
                            .navigationTitle(navi_list)
                            .navigationBarTitleDisplayMode(.inline)

                    case "SwipeDetailView":
                        SwipeDetailView(path: $path)
                            .navigationTitle(navi_list)
                            .navigationBarTitleDisplayMode(.inline)

                    case "CarouselView":
                        CarouselView(path: $path)
                            .navigationTitle(navi_list)
                            .navigationBarTitleDisplayMode(.inline)

                    case "BaseTabView":
                        BaseTabView(path: $path)
                            .navigationTitle(navi_list)
                            .navigationBarTitleDisplayMode(.inline)

                    case "SelectTabView":
                        SelectTabView(path: $path)
                            .navigationTitle(navi_list)
                            .navigationBarTitleDisplayMode(.inline)

                    case "TabBarView":
                        Text(navi_list)
                            .font(.system(size: 22))
                            .foregroundColor(.orange)
                            .padding()

                    default:
                        Text(navi_list)
                            .font(.system(size: 22))
                            .foregroundColor(.blue)
                            .padding()
                    }
                }
                .navigationTitle("NavigationList")
                .font(.system(size: 22))
                .foregroundColor(.purple)
                
            }
        }

        Button(action: {
            withAnimation {
                self.isPopupView_5.toggle()
            }
        }, label: {
            Text("Close")
                .font(.title2)
                .foregroundColor(.black)
        })
        .padding()
    }
}

#Preview {
    NavigationListView(isPopupView_5: .constant(false))
}

追記・修正をした部分を説明します。

  • var navi_lists: [String] = […“BaseTabView”…]:TabVeiw から BaseTabVeiw に修正しています。
  • case “BaseTabView”:TabVeiw から BaseTabVeiw に修正しています。
  • BaseTabView(path: $path):BaseTabView に、path の引数を渡しています。
  • SelectTabView(path: $path):SelectTabView に、path の引数を渡しています。

 

BaseTabView → SelectTabView

BaseTabVeiw と SelectTabView のソースを紹介して行きますが、今回も事前に、

Viewで利用する画像(写真)をXcode に追加して行きます。

 

画像(写真)の追加

著作権や肖像権を考慮して、今回も、筆者の家で飼っている猫の写真を利用します。

Finder から、好きな猫の写真をドラッグ&ドロップします。

画像(写真)は、著作権や肖像権の問題が無いものを、各環境に合わせて、投入して下さい。

 

今回も、6枚の猫の写真をドラッグ&ドロップしてみました。

もし、画像(写真)の名称をそのまま利用したく無い場合は、Assets 内で名称を

変更する事も出来ますので、試してみて下さい。

これで、SwiftUI のソースから直接、画像(写真)を呼び出して利用する事が出来ます。

 

 

ソース例:BaseTabView

NavigationStack から選択して、画像を回転させたり、3回押した(トリプルタップした)

時のアクションを実現する BaseTabVeiw のソースを紹介します。

 


import SwiftUI

struct BaseTabView: View {

    @Binding var path: [String]
    @State private var magnifyBy: CGFloat = 1.0
    @State private var angle = Angle(degrees: 0.0)
    @State private var minZoom: CGFloat = 0.3
    @State private var maxZoom: CGFloat = 1.0

    var body: some View {
        ZStack {
            Color.white
                .edgesIgnoringSafeArea(.all)

            VStack {

                TabView {

                    Image("IMG_0541")
                        .resizable()
                        .scaledToFit()
                        .rotationEffect(angle)
                        .gesture(
                            RotateGesture()
                                .onChanged { value in
                                    angle = value.rotation
                                }
                        )
                        .tabItem {
                            Text("Angle")
                        }

                    Image("IMG_0232")
                        .resizable()
                        .scaledToFit()
                        .scaleEffect(magnifyBy)
                        .gesture(
                            TapGesture(count: 3)
                                .onEnded {
                                    withAnimation {
                                        magnifyBy = magnifyBy < maxZoom ? maxZoom:minZoom
                                    }
                                }
                        )
                        .tabItem {
                            Text("Zoom")
                        }
                }
                .padding()

                Button(action: {
                    withAnimation {
                        path.append("SelectTabView")
                    }
                }, label: {
                    Text("SelectTabView")
                        .font(.system(size: 22))
                        .foregroundColor(.mint)
                })
            }
        }
    }
}

#Preview {
    BaseTabView(path: .constant(["BaseTabView"]))
}
  • @State private var magnifyBy:ズームの変化量を宣言しています。
  • @State private var angle = Angle(degrees: 0.0):角度の変化量を宣言しています。
  • @State private var minZoom:ズームの最小値を宣言しています。
  • @State private var maxZoom:ズームの最大値を宣言しています。
  • TabView {:基本的な TabView を設定します。

 

RotateGesture

画像を回転させる為に、RotateGesture() を利用します。

  • Image(“IMG_0541”):猫の写真の画像を設定しています。
  • .rotationEffect(angle):rotationEffect に、回転した angle をバインドすることで、自動的に任意の角度に回転します。
  • .gesture(:gesture のアクションを記載します。
  • RotateGesture():RotateGesture を呼び出しています。
  • .onChanged { value in:gesture が、変化した時のアクションを記載します。
  • angle = value.rotation:angle に、回転させた変化量を代入しています。
  • .tabItem { Text(“Angle”):Tab 内に、Angle のテキスト(文字)を設定しています。

 

TapGesture(count: 3)

連続したタップアクションを実現する為に、TapGesture(count: 3)  を利用します。

  • Image(“IMG_0232”):猫の写真の画像を設定しています。
  • .scaleEffect(magnifyBy):scaleEffect に、ズームした magnifyBy をバインドすることで、自動的にズームを実現します。
  • .gesture(:gesture のアクションを記載します。
  • TapGesture(count: 3):TapGesture() を呼び出しています。count: 3で、3回(トリプル)の Tap 動作を検知した時に、アクションを実行します。
  • .onEnded {:gesture が、終了した時のアクションを記載します。
  • magnifyBy = magnifyBy < maxZoom ? maxZoom:minZoom:magnifyBy の値が maxZoom より小さい場合は、magnifyBy に maxZoom を代入しています。magnifyBy の値が maxZoom より小さく無い場合は、magnifyBy に minZoom を代入しています。
  • .tabItem { Text(“Zoom”):Tab 内に、Zoom のテキスト(文字)を設定しています。

 

今回は、シンプル(ベーシック)な TabVeiw を表現しますので、Tab を装飾させる為の

.tabViewStyle() や  .indexViewStyle() は、実装していません。

 

ソース例:SelectTabView

BaseTabVeiw から遷移して、子Veiw が6つ以上になると、More というリストの Tab

になり、5つ目以降のTabが格納される SelectTabView のソースを紹介します。

 


import SwiftUI

struct SelectTabView: View {

    @Binding var path: [String]

    var imageNames: [String] = [
        "IMG_0981",
        "IMG_0059",
        "IMG_0232",
        "IMG_0688",
        "IMG_0541",
        "IMG_0058",
    ]

    var body: some View {
        ZStack {
            Color.white
                .edgesIgnoringSafeArea(.all)

            VStack {

                TabView {

                    ForEach(imageNames, id: \.self) { imageName in
                        withAnimation {

                            VStack {

                                Image(imageName)
                                    .resizable()
                                    .scaledToFit()

                            }
                        }
                        .tabItem {
                            Label(imageName, systemImage: "cat")
                        }
                    }
                }

                Button(action: {
                    withAnimation {
                        path.removeAll()
                    }
                }, label: {
                    Text("NavigationListView")
                        .font(.system(size: 22))
                        .foregroundColor(.red)
                })
            }
        }
    }
}

#Preview {
    SelectTabView(path: .constant(["SelectTabView"]))
}
  • var imageNames: [String] = [“IMG_0981”, “IMG_0059”,…]:Assets に保存した複数の画像(写真)の名称を利用する為に、配列で宣言しています。
  • ForEach(imageNames, id: \.self) { imageName in:Assets に保存した画像(写真)を、ForEach()で配列の画像(写真)を繰り返し生成して表示します。
  • TabView {:基本的な TabView を設定します。
  • Image(imageName):配列から画像(写真)の名称を利用して、表示しています。
  • .tabItem { Label(imageName, systemImage: “cat”) }:Tab 内に、画像(写真)の名称と、SF Symbols の猫のアイコンを設定しています。

 

 

TabVeiw:前回のおさらい

TabView とは、複数の 子View を切り替えることのできる View です。

SwiftUI の Veiw は、アプリの画面に表示されるものを指すので、画面や画像だけではなく、

ボタンや文字(テキスト)なども切り替えて、表示することが出来ます。

表示方法は、TabView {} の中に、View を入れる形になります。

上記のソースのように、TabView { Text(”…”)  Image(”…”) } の要領です。

Tab ですが、最大5つまで表示させる事が出来ます。Tab が6つ以上になると、More という

リストの Tab になり、5つ目以降の Tab が格納されます。

 

 

アプリ実行:シミュレータ起動

では、実際にアプリを実行してみます。

Xcode の上部タブメニューより、Product → Run を選択するか、command + R を

押してみて下さい。

 

エラーが無ければ、iPhoneシミュレータが起動します。

 

 

ContntVeiw(メインメニュー)が表示されました。

 

NavigationListView:ナビゲーション画面

続けて、NavigationListViewボタンを押してみます(タップします)。

 

 

ContntVeiw(メインメニュー)の上に NavigationListVeiw(ナビゲーション機能)が

重なって、表示されました。

 

NavigationList の内容は、下記の通り TabVeiw から BaseTabVeiw に変更されている

事が確認出来ます。

  • SwipeView
  • CarouselView
  • BaseTabVeiw
  • TabBarView

現在の 配列(path) の状態は、下記になります。

  • path = []

 

BaseTabView:画像の回転&トリプルタップ&Liquid Glass

次に、NavigationList の3段目の BaseTabView を押してみます(タップします)。

 

 

画面上部には、BaseTabView のタイトルが表示されていますが、

NavigationListVeiw で、タイトルを表示するように設定していました。

 

猫の画像の下には、iOS26.0以降から採用された、ガラスっぽい見た目?の

大きなTab の Liquid Glass(青枠)が確認出来ます。

左側は、Angle と記載された選択中の大きな Tab(背景:グレー色)で、

右側は、Zoom と記載された大きな Tab(背景:白色)です。

 

現在の 配列(path) の状態は、下記になります。

  • path = [“BaseTabView“]

 

では、猫の画像を回転させてみます。

SwipeView のピンチイン、ピンチアウト(縮小・拡大)を実現した時のように、

画像の上で、キーボードの optionキー を押した状態で、トラックパッドに

2本の指で触れて、時計回りに回転させてみて下さい。

 

 

猫の画像が、斜めに回転した事が確認出来ます。

更に、猫の画像を時計回りに回転させてみます。

 

 

猫の画像が、斜め下向きに回転した事が確認出来ます。

 

今度は、猫の画像の下にある大きな Tab の右側(Zoom)を押して(タップして)、

画面を切り替えてみます。

 

 

左側は、Angle と記載された大きな Tab(背景:白色)で、

右側は、Zoom と記載された選択中の大きな Tab(背景:グレー色)

に切り替わった事が確認出来ます。

 

ここでは、猫の画像を3回連続で叩いて(トリプルタップして)みます。

 

 

猫の画像が、縮小(約30%)された事が確認出来ます。

ここで、猫の画像を3回連続で叩いて(トリプルタップして)みると、

元の大きさに戻る事も確認出来ます。

 

 

ソースに、withAnimation {} の効果を入れているので、画像がスムースに縮小・拡大されて

いる事も確認が出来ると思います。

BaseTabView では、猫の画像の任意の回転とトリプルタップによる縮小・拡大を確認しました。

 

SelectTabView:複数Tabの表示

次に、大きな Tab の下に配置されている SelectTabView ボタンを押して(タップして)、

SelectTabView を表示します。

 

 

BaseTabView と似た画面に見えますが、猫の画像の下にある大きな Tab が複数見え、

Tab の中には猫のアイコンが確認出来ます。

画面上部には、SelectTabView のタイトルが表示されていますが、

NavigationListVeiw で、タイトルを表示するように設定していました。

 

 

左端の Tab の猫のアイコン&画像名のみ、選択中の青色(背景:グレー色)が確認出来ます。

右端の Tab の中には、点々「…」&More(紫枠)の文字が確認出来ます。

こちらは、TabView の説明でも記載していますが、Tab が6つ以上になると、More という

リストの Tab になり、5つ目以降の Tab が格納されている状態です。

 

現在の 配列(path) の状態は、下記になります。

  • path = [“BaseTabView“, “SelectTabView”]

 

次に、左端から2番目の Tab を押して(タップして)みます。

 

左端から2番目の Tab の猫のアイコン&画像名のみ、選択中の青色(背景:グレー色)が

確認出来ます。

 

次に、左端から3番目の Tab を押して(タップして)みます。

 

 

左端から3番目の Tab の猫のアイコン&画像名のみ、選択中の青色(背景:グレー色)が

確認出来ます。

こちらの画像は、BaseTabView でトリプルタップによる縮小・拡大を確認した

画像と同じです。

 

次に、左端から4番目の Tab を押して(タップして)みます。

 

左端から4番目の Tab の猫のアイコン&画像名のみ、選択中の青色(背景:グレー色)が

確認出来ます。

 

SelectTabView:More

では、今度は、右端の点々「…」&More の Tab を押して(タップして)みます。

 

 

横にスライドして、猫のアイコン&画像名のリスト画面が表示されました。

画面上部には、SelectTabView のタイトルの下に、More のサブタイトルが

表示されています。

また、右端の Tab のみ選択されていて、点々「…」&Moreの文字が

青色(背景:グレー色)となっている事が確認出来ます。

 

では、上段のリストの猫のアイコン&IMG_0541 を押して(タップして)みます。

 

横にスライドして、5枚目の猫の画像が表示されました。

こちらの画像は、BaseTabView で猫の画像を任意の角度に回転させた画像と同じです。

 

では、More のリスト画面に戻ります。

 

 

画面の左上に、「<」マークが縦に2段に並んでいる事が確認出来ますので、2段目の

「<」マーク(緑枠)を押す(タップする)と、More のリストに戻ります。

間違って、上段の「<」マークを押す(タップする)と、BaseTabView に戻りますので、

ご注意下さい。

 

 

では、2段目のリストの猫のアイコン&IMG_0058 を押して(タップして)みます。

 

 

横にスライドして、6枚目の猫の画像が表示されました。

 

SelectTabView では、複数の Tab 表示とMoreリスト画面を確認しました。

 

まとめ

今回は、SwiftUIで、ベーシックなTabViewと画像を回転させるアプリケーション

を開発記事を紹介しました。

 

次回は、SwiftUIで、TabBarとPickerを利用したアプリケーションを開発する記事を

紹介しようと思います。

 

コメント

タイトルとURLをコピーしました