SwiftUIの基礎学習とUIKitとの連携【iOS】

先日SwiftUIに初めて触れてシンプルなコードでUIが実装できることにとても魅力を感じました。

SwiftUIについてもっと詳しく調べたくなったので、基礎的なことを組み合わせてどんな事ができるのかもう少し詳しく調べてみたいと思います。

環境

・MacOS Ventura 13.0
・Xcode 14.2
・Swift version 5.7.2

1. 画像を並べてスクロール可能にする

animal1~animal3画像を作成しました。これらを2列に並べて縦スクルロールしてみます。


1-1. GeometoryReaderの追加

body内にGeometoryReaderを追加します。

GeometoryReaderを使うと親Viewのサイズを取得することが可能になります。

例) geometory.size.width

GeometryReader { geometory in
}

1-2. ScrollViewの追加

GeometoryReader内に縦スクルロールのScrollViewを追加します。

ScrollView(.vertical, showsIndicators: false ) {
}

1-3. 画像を並べる

ScrollView内に画像を並べます。

VStackで縦並びにした後ループ内で横並び(2列)に画像を表示しています。

frameのheightの設定は横画面に対応するための処理です。

                VStack {
                    ForEach(1..<4) { i in
                        
                        HStack {
                            Image("animal\(i)").resizable().frame(width: geometory.size.width / 2)
                            Image("animal\(i)").resizable().frame(width: geometory.size.width / 2)
                        }.frame(height: UIDevice.current.orientation.isLandscape ? 300 : 150)
                    }
                }

1-4. 完成

レイアウトが完成しました。縦スクルロールもプレビューで確認できます。


2. SwiftUIでUIKitを使う

検索バー(UISearchbar)に文字列を入力して一致するもののみ表示するサンプルを作ってみます。

UIKitをのViewを使うためにはUIViewRepresentableを使用します。

2-1. UISearchBarを表示するための構造体を作成する

SearchViewという構造体を追加してUIViewRepresentableを参照します。

makeUIViewとupdateUIViewは必ず必要なので予め追加しておきます。

makeUIView

makeUIViewではUISearchBarのインスタンスを生成しています。

func makeUIView(context: Context) -> UISearchBar {
        let searchbar = UISearchBar()
        searchbar.barStyle = .default
        searchbar.autocapitalizationType = .none
        searchbar.delegate = context.coordinator
        return searchbar
    }

updateUIView

データが更新された際にUIViewにデータを反映させる処理をここに追加します。

今回は何も処理はしません。

func updateUIView(_ uiView: UISearchBar, context: Context) {
        
    }

Coordinator

Coordinatorクラスでは検索バーに入力された文字列を受けています。

UIKitのイベント全般を管理するクラスです。

@Binding var txt : String

func makeCoordinator() -> Coordinator {
        return Coordinator(parent: self)
    }

class Coordinator : NSObject,UISearchBarDelegate {
        var parent : SearchView!
        
        init(parent: SearchView!) {
            self.parent = parent
        }
        
        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            
            parent.txt = searchText
        }
    }

以上で構造体SearchViewは完成しました。

SarchViewとListを使って実際に文字列を検索してみます。

2-2. 文字列一致検索と動作確認

レイアウトは以下のようになりました。


ABCDの配列をListに追加して検索をしてみます。

VStackで縦並びのレイアウトにした後にSearchViewとListを配置しています。

SearchViewに入力された文字列と配列内に含まれる文字列の一致検索が問題なく動作しました。



変数部分とbodyのコードは以下になります。

(localizedStandardContainsは大文字と小文字の区別をつけない設定です。)

    @State var datas = ["A", "B", "C","D"]
    @State var txt = ""
    var body: some View {
        VStack {
            SearchView(txt: $txt)
            
            List(datas.filter{ txt == "" ? true : $0.localizedStandardContains(txt)},id: \.self){ i in
                Text(i).fontWeight(.heavy)
            }
        }
    }

SwiftUIでUIKitを扱うサンプルが完成しました。

3. ContextMenuを使ってみる

ContextMenuはViewを長押ししたとにメニューを表示する機能です。

画像を長押ししたらメニューが表示されるサンプルを作ってみます。

3-1. コードの説明

画面の真ん中に画像を配置しています。

画像のリサイズを可能にして、サイズは縦横どちらも100に指定しています。

アスペクト比が崩れないように.fitを設定しています。

最後に画像に対して.contextMenuを追加することで長押し時にメニューの表示ができるようになります。


Menuの表示内容はVStackで縦並びに指定した後にボタンを2個配置しています。

削除ボタンと保存ボタンとしました。

画像はシステムアイコンを使用しています。


SF SymbolsをApple公式サイトからダウンロードしておくとシステムアイコンを探す時に便利です。

https://developer.apple.com/jp/sf-symbols/

        Image("animal1").resizable().frame(width: 100, height: 100).aspectRatio( contentMode: .fit)
            .contextMenu {
                
                VStack {
                    Button {
                        print("削除")
                    } label: {
                        Image(systemName: "trash")
                        Text("削除")
                    }
                    Button {
                        print("保存")
                    } label: {
                        Image(systemName: "folder")
                        Text("保存")
                    }
                }
            }

4. ImagePickerを使ってみる

ImagePickerを使って、写真アプリから選択した写真を画面に表示する機能を作ってみます。

UIViewControllerRepresentableを参照してImagePickerと言う構造体を作ります。

4-1. makeUIViewController

makeUIViewControllerではUIImagePickerControllerのインスタンスを生成しています。

func makeUIViewController(context: Context) -> UIImagePickerController {
        
        let controller = UIImagePickerController()
        controller.sourceType = .photoLibrary
        controller.delegate = context.coordinator
        return controller
    }

4-2. updateUIViewController

updateUIViewControllerは今回は何もしません。

コードを書いておかないとエラーになるため追加します。

func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        
    }

Coordinator

CoordinatorではUIImagePickerControllerのイベントの受け取りと変数へのデータのセットを行っています。

変数には@Bindingを指定することで変更をViewに反映することができるようになります。

class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        
        @Binding var shown : Bool
        @Binding var imgData: Data
        
        init(imgData1: Binding, shown1: Binding) {
            _imgData = imgData1
            _shown = shown1
        }
        
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            shown.toggle()
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            let image = info[.originalImage] as! UIImage
            
            imgData = image.jpegData(compressionQuality: 80)!
            shown.toggle()
        }
    }

@Binding var shown : Bool
    @Binding var imgData: Data
    
    func makeCoordinator() -> ImagePicker.Coordinator {
        return Coordinator(imgData1: $imgData, shown1: $shown)
    }

レイアウトと変数の設定

レイアウトはImageとButtonだけのシンプルなレイアウトにします。

変数は画像のデータ保持用とPickerの表示非表示を管理する変数のみとなります。

@State var imageData = Data.init(count: 0)
    @State var shown = false
    
    var body: some View {
        
        VStack {
            
            if imageData.count != 0 {
                Image(uiImage: UIImage(data: imageData)!).resizable().frame(height: 300).padding().cornerRadius(20)
            }
            
            Button("Sheetを開く") {
                self.shown.toggle()
            }
            .sheet(isPresented: $shown) {
                Image

5. まとめ

いかがでしたでしょうか。

今回はSwiftUIの基礎を学びながら簡単なアプリを作ってみました。

UIkitとの連携が主な内容となりました。

UIKitとの連携部分はコードの量が増えてしまいますが、一回覚えてしまえばパターンは同じような感じで実装できるので、SwiftUIメインでアプリを作るのも良さそうに感じました。

参考になる部分があれば幸いです。