[swift] APIを使った検索機能の実装

お菓子の虜 web APIの情報を使ってiOSで表示する

お菓子の虜 Web API

e.g.: https://www.sysbird.jp/webapi/?apikey=guest&keyword=%E3%82%AB%E3%83%AC%E3%83%BC%E5%91%B3&format=json
https://www.sysbird.jp/webapi/?apikey=guest&keyword=%E3%82%AB%E3%83%AC%E3%83%BC%E5%91%B3&format=json&max=10

request parameters: id, type, year, keyword, max, order
response: status, count, item(id, name, maker, price, type, regist, url, image, comment)

@StateObject, @State, @Bindingを学ぶ
検索画面はContentView.swiftで一覧表示処理、OkashiData.swiftでカスタムクラスで実装

OkashiData.swift
L StructではObservableOjectは利用できない、 classで定義する必要がある
  L ObservableOjectはカスタムクラス内でデータの状態を管理するために利用

class OkashiData: ObservableObject {
    
    func searchOkashi(keyword: String) async {
        print(keyword)
    }
}

Taskは一連の流れを処理する
ContentView.swift

struct ContentView: View {
    
    @StateObject var okashiDataList = OkashiData()
    @State var inputText = ""
    
    var body: some View {
        VStack {
            TextField("キーワード", text: $inputText,
                      prompt: Text("キーワードを入力してください。"))
                .onSubmit {
                    Task {
                        await okashiDataList.searchOkashi(keyword: inputText)
                    }
                }
                .submitLabel(.search)
                .padding()
        }
    }
}

WebAPIのリクエストURLを組み立てる
OkashiData.swift

    func searchOkashi(keyword: String) async {
        print(keyword)
        
        guard let keyword_encode = keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
            return
        }
        
        guard let req_url = URL(string:
            "https://www.sysbird.jp/webapi/?apikey=guest&format=json&keyword=\(keyword_encode)&max=10&order=r") else {
            return
        }
        print(req_url)
    }

レスポンスデータ(JSON)を記憶する構造体
L Itemとすることで複数の構造体を保持できる配列として保存
  L ?を付与してnilを許容するオプショナル型として宣言する

    struct ResultJson: Codable {
        struct Item: Codable {
            let name: String?
            let url: URL?
            let image: URL?
        }
        let item: [Item]?
    }

URLSessionでデータをダウンロード
URLSession.sharedで簡素に実行

        do {
            let(data, _) = try await URLSession.shared.data(from: req_url)
            
            let decoder = JSONDecoder()
            let json = try decoder.decode(ResultJson.self, from: data)
            
            print(json)
        } catch {
            print("エラーが出ました")
        }

最近のiOSはマルチコアプロセッサが搭載されている

### 取得したデータをListで一覧表示
Itemの構造体を作成し、List表示
L Identifiableに準拠すると、一意に識別できる型として定義できる
  L uuidを用いてランダムな一意の値を生成

import SwiftUI
import UIKit

struct OkashiItem: Identifiable {
    let id = UUID()
    let name: String
    let link: URL
    let image: URL
}

@StateObject、ObservableObjectを使用すると@Publishedを使用できる
プロパティラッパーはプロパティをラップして機能を追加する

            guard let items = json.item else {return}
            
            DispatchQueue.main.async {
                self.okashiList.removeAll()
            }
            
            for item in items {
                if let name = item.name,
                   let link = item.url,
                   let image = item.image {
                   let okashi = OkashiItem(name: name, link: link, image: image)
                    DispatchQueue.main.async {
                        self.okashiList.append(okashi)
                    }
                }
            }
            print(self.okashiList)

### リストで一覧表示

    var body: some View {
        VStack {
            TextField("キーワード", text: $inputText,
                      prompt: Text("キーワードを入力してください。"))
                .onSubmit {
                    Task {
                        await okashiDataList.searchOkashi(keyword: inputText)
                    }
                }
                .submitLabel(.search)
                .padding()
            List(okashiDataList.okashiList) { okashi in
                HStack {
                    AsyncImage(url: okashi.image) { image in
                        image
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(height: 40)
                    } placeholder: {
                        ProgressView()
                    }
                    Text(okashi.name)
                }
            }
        }
    }

### Webページの表示
SFSafariViewControllerでWebページを表示

SafariView.swift
L SafariServicesでアプリの中でsafariを起動する

import SafariServices

struct SafariView: UIViewControllerRepresentable {
    
    var url: URL
    
    func makeUIViewController(context: Context) -> SFSafariViewController {
        return SFSafariViewController(url: url)
    }
    
    func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context){
        
    }
}

struct ContentView: View {
    
    @StateObject var okashiDataList = OkashiData()
    @State var inputText = ""
    @State var showSafari = false
    
    var body: some View {
        VStack {
            TextField("キーワード", text: $inputText,
                      prompt: Text("キーワードを入力してください。"))
                .onSubmit {
                    Task {
                        await okashiDataList.searchOkashi(keyword: inputText)
                    }
                }
                .submitLabel(.search)
                .padding()
            List(okashiDataList.okashiList) { okashi in
                
                Button(action: {
                    showSafari.toggle()
                }){
                    HStack {
                        AsyncImage(url: okashi.image) { image in
                            image
                                .resizable()
                                .aspectRatio(contentMode: .fit)
                                .frame(height: 40)
                        } placeholder: {
                            ProgressView()
                        }
                        Text(okashi.name)
                    }
                }
            }
        }
    }
}

これは凄い

[swift] エフェクト編集画面の作成

EffectView.swift

struct EffectView: View {
    
    @Binding var isShowSheet: Bool
    
    let captureImage: UIImage
    
    @State var showImage: UIImage?
    @State var isShowActivity = false
    
    var body: some View {
        
        VStack {
            Spacer()
            
            if let unwrapShowImage = showImage {
                Image(uiImage: unwrapShowImage)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            }
            
            Spacer()
            
            Button(action: {}){
                Text("エフェクト")
                    .frame(maxWidth: .infinity)
                    .frame(height: 50)
                    .multilineTextAlignment(.center)
                    .background(Color.blue)
                    .foregroundColor(Color.white)
            }
            .padding()
            
            Button(action: {}){
                Text("閉じる")
                    .frame(maxWidth: .infinity)
                    .frame(height: 50)
                    .multilineTextAlignment(.center)
                    .background(Color.blue)
                    .foregroundColor(Color.white)
            }
            .padding()
        }
        .onAppear {
            showImage = captureImage
        }
    }
}

struct EffectView_Previews: PreviewProvider {
    static var previews: some View {
        EffectView(
            isShowSheet: Binding.constant(true),
            captureImage: UIImage(named: "preview_use")!)
    }
}

CoreImageに多数の編集機能がある

なるほど、カメラ機能の実装はiPhone端末がないとダメだな

[swift] シェア画面を追加

ActivityView.swift
L Anyは任意の値を表すデータ型、どんな型でもOK

import SwiftUI

struct ActivityView: UIViewControllerRepresentable {
    
    let shareItems: [Any]
    
    func makeUIViewController(context: Context) ->
    UIActivityViewController {
        
    let controller = UIActivityViewController(
        activityItems: shareItems,
        applicationActivities: nil)
        
        return controller
    }
    
    func uupdateUIViewController(
        _ uiViewController: UIActivityViewController,
        context: UIViewControllerRepresentableContext<ActivityView>){
            
        }
}

ContentView.swift
 L 写真がないこともある変数をオプショナル変数という アンラップ処理をする

            Button(action: {
                if let _ = captureImage{
                    isShowActivity = true
                }
            }){
                Text("SNSに投稿する")
                    .frame(maxWidth: .infinity)
                    .frame(height: 50)
                    .multilineTextAlignment(.center)
                    .background(Color.blue)
                    .foregroundColor(Color.white)
            }
            .padding()
            .sheet(isPresented: $isShowActivity){
                ActivityView(shareItems: [captureImage!])
            }
        }

### フォトライブラリから写真を取り込めるようにする
PHPickerViewControllerはPhotoKitで提供されている機能
coordinatorを使用する

struct PHPickerView: UIViewControllerRepresentable {
    
    @Binding var isShowSheet: Bool
    @Binding var captureImage: UIImage?
    
    class Coordinator: NSObject,
                       PHPickerViewControllerDelegate {
        var parent: PHPickerView
        
        init(parent: PHPickerView){
            self.parent = parent
        }
        
        func picker(
            _ picker: PHPickerViewController,
            didFinishPicking results: [PHPickerResult]){
                
                if let result = results.first {
                    result.itemProvider.loadObject(ofClass: UIImage.self){
                        (image, error) in
                        
                        if let unwrapImage = image as? UIImage {
                            self.parent.captureImage = unwrapImage
                        } else {
                            print("使用できる写真がないです")
                        }
                    }
                } else {
                    print("選択された写真はないです")
                }
                parent.isShowSheet = false
            }
    }
}
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    func makeUIViewController(
        context:UIViewControllerRepresentableContext<PHPickerView>)
    -> PHPickerViewController {
        var configuration = PHPickerConfiguration()
        configuration.filter = .images
        configuration.selectionLimit = 1
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = context.coordinator
        return picker
    }
    
    func updateUIViewController(
        _ uiViewController: PHPickerViewController,
        context: UIViewControllerRepresentableContext<PHPickerView>){
            
        }

### カメラとフォトライブラリーの選択画面

            .actionSheet(isPresented: $isShowAction){
                ActionSheet(title: Text("確認"),
                            message: Text("選択してください"),
                            buttons: [
                                .default(Text("カメラ"), action: {
                                    isPhotolibrary = false
                                    
                                    if UIImagePickerController.isSourceTypeAvailable(.camera){
                                        print("カメラは利用できます")
                                        isShowSheet = true
                                    } else {
                                        print("カメラは利用できません")
                                    }
                                }),
                                .default(Text("フォトライブラリー"), action: {
                                    isPhotolibrary = true
                                    isShowSheet = true
                                }),
                                .cancel()
                            ])
            }

なるほど、カメラを使えると中々面白い

[swift] カメラアプリを作ろう1

– カメラが起動し撮影できる
– SNSなどでシェアできる

カメラの起動はUIImagePickerControllerクラス、Coordinatorを使う
delegateメソッドを使って撮影後の写真を画面に表示できる

ContentView.swift, ImagePickerView.swift, ActivityView.swiftを作成する
UIKitはiOS開発に中核となるコントロール群

ImagePickerView.swiftを作成
L UIKitは自動的にimportされている
  L 写真と撮影画面を閉じるフラグ設定
L UIImageは画像を管理するクラス、 Coordinator機能を利用する

import SwiftUI

struct ImagePickerView: UIViewControllerRepresentable {
    @Binding var isShowSheet: Bool
    
    @Binding var captureImage: UIImage?
}

coordinator class追加

class Coordinator: NSObject,
                UINavigationControllerDelegate,
                       UIImagePickerControllerDelegate {
        
        let parent: ImagePickerView
        
        init(_ parent: ImagePickerView){
            self.parent = parent
        }
        
        func imagePickerController(
            _ picker: UIImagePickerController,
            didFinishPickingMediaWithInfo info:
            [UIImagePickerController.InfoKey : Any]){
                
            if let originalImage =
                info[UIImagePickerController.InfoKey.originalImage]
                as? UIImage {
                parent.captureImage = originalImage
                }
                parent.isShowSheet = false
            }
        
        func imagePickerControllerDidCancel(
            _ picker: UIImagePickerController) {
                parent.isShowSheet = false
            }
    }

UIImagePickerController.InfoKey.originalImageでカメラで撮影した写真が取得できる

### Coordinator classとUIViewControllerRepresentable

    func makeUIViewController (
        context: UIViewControllerRepresentableContext<ImagePickerView>) ->
    UIImagePickerController {
        
        let myImagePickerController = UIImagePickerController()
        myImagePickerController.sourceType = .camera
        myImagePickerController.delegate = context.coordinator
        return myImagePickerController
    }
    
    func updateUIViewController(
        _ uiViewController: UIImagePickerController,
        context: UIViewControllerRepresentableContext<ImagePickerView>){
            
        }

カメラを使用するときにはカメラの動きを指示するオプションを設定する
プロパティは sourceType, mediaType, cameraDevice, cameraFlashModeなどがある

delegateとは、あるクラスで行いたい処理の一部を他のクラスに任せたり、任せた処理を指定したクラスに通知する仕組み
protocolから処理を依頼されるクラスがある

### カメラの起動処理
カメラのプロパティを設定する行を追加
Privacy – Camera Usage Description : 写真を撮影するためにカメラを利用します。

プロパティリストはアプリの稼働に必要な設定情報を管理

### カメラ起動処理

struct ContentView: View {
    var body: some View {
        VStack {
            Spacer()
            Button(action: {
                if UIImagePickerController.isSourceTypeAvailable(.camera){
                    print("カメラは使用できます")
                } else {
                    print("カメラは使用できません")
                }
            }){
                Text("カメラを起動する")
                    .frame(maxWidth: .infinity)
                    .frame(height: 50)
                    .multilineTextAlignment(.center)
                    .background(Color.blue)
                    .foregroundColor(Color.white)
            }
        }
    }
}

### カメラを起動して撮影

@State var captureImage: UIImage? = nil
@State var isShowSheet = false

// 省略

            Spacer()
            if let unwrapCaptureImage = captureImage {
                
                Image(uiImage: unwrapCaptureImage)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            }
// 省略
            Button(action: {
                if UIImagePickerController.isSourceTypeAvailable(.camera){
                    print("カメラは使用できます")
                    isShowSheet = true
                } else {
                    print("カメラは使用できません")
                }
            }){
                Text("カメラを起動する")
                    .frame(maxWidth: .infinity)
                    .frame(height: 50)
                    .multilineTextAlignment(.center)
                    .background(Color.blue)
                    .foregroundColor(Color.white)
            }
// 省略
            .sheet(isPresented: $isShowSheet){
                ImagePickerView(
                    isShowSheet: $isShowSheet,
                    captureImage: $captureImage)
            }

カメラは覚えることが多いが、アプリで一番面白そうな分野ではある

[swift] タイマー処理と設定した秒数

@AppStorageはデータを永続化するUserDefaultsから値を読み込みする
UserDefaultはアプリで利用する値を保存する機能
L ここではtimer_valueというkeyにtimeValueの初期時10を導入している

    @State var timerHandler : Timer?
    @State var count = 0
    @AppStorage("timer_value") var timerValue = 10

1秒ごとに呼び出してcountを+1とし、残り0でタイマーを止める

    func countDownTimer() {
        count += 1
        
        if timerValue - count <= 0 {
            timerHandler?.invalidate()
        }
    }

タイマー開始

    func startTimer() {
        if let unwrapedTimerHandler = timerHandler {
            if unwrapedTimerHandler.isValid == true {
                return
            }
        }
        
        if timerValue - count <= 0 {
            count = 0
        }
        
        timerHandler = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){
            _ in
            
            countDownTimer()
        }
    }

### タイマーの保存
SettingView.swift

@AppStorage("timer_value") var timerValue = 10

シミュレータで確認する

### アラートの表示

            .alert(isPresented: $showAlert){
                Alert(title: Text("終了"),
                      message: Text("タイマー終了時間です"),
                      dismissButton: .default(Text("OK")))
            }

すげえええええええええええ

[swift] タイマーアプリを作ろう

スタート、ストップボタンとカウントダウンの秒数を表示させるテキストを配置
スタートでカウントダウンを開始再開、ストップで一時停止
秒数設定で終了時間を設定できる
画面遷移はNavigationViewを利用、カウントダウン開始秒数をpickerを配置して設定 Backでタイマー画面に戻る

ContentView.swift
L NavigationViewは先頭画面であることを宣言
  L .toolbarはボタンを配置
  L navigationBarTrailingで右側に配置 navigationBarLeadingは右側、.bottomBarは下部

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("タイマー画面")
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing){
                    NavigationLink(destination: SettingView()){
                        Text("秒数設定")
                    }
                }
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

### タイマーの色を定義
Asset.xcassetsでcolorsetを追加、「Any Appearance」を選択
218, 78, 122にRGBを設定
Darkモードで208, 68, 112で設定

同様にストップの色も定義する

ZStack {
                Image("backgroundTimer")
                    .resizable()
                    .ignoresSafeArea()
                    .aspectRatio(contentMode: .fill)
                VStack(spacing: 30.0) {
                    Text("残り10秒")
                        .font(.largeTitle)
                    HStack {
                        Button(action: {}){
                            Text("スタート")
                                .font(.title)
                                .foregroundColor(Color.white)
                                .frame(width: 140, height: 140)
                                .background(Color("startColor"))
                                .clipShape(Circle())
                        }
                        Button(action: {}){
                            Text("ストップ")
                                .font(.title)
                                .foregroundColor(Color.white)
                                .frame(width: 140, height: 140)
                                .background(Color("stopColor"))
                                .clipShape(Circle())
                        }
                    }
                }
            }

### Pickerを配置
SettingView.swift
L pickerStyle(.wheel)でPickerホイール配置

struct SettingView: View {
    
    @State var timerValue = 10
    
    var body: some View {
        
        ZStack {
            Color("backgroundSetting")
                .ignoresSafeArea()
            
            VStack {
                
                Picker(selection: $timerValue){
                    Text("10")
                        .tag(10)
                    Text("20")
                        .tag(20)
                    Text("30")
                        .tag(30)
                    Text("40")
                        .tag(40)
                    Text("50")
                        .tag(50)
                    Text("60")
                        .tag(60)
                } label: {
                    Text("選択")
                }
                .pickerStyle(.wheel)
            }
        }
    }
}

うむ、覚えることが広いな

[swift] マップ検索アプリでMapKitとクロージャを学習2

ContentView.swift

struct ContentView: View {
    
    @State var inputText: String = ""
    @State var dispSearchKey: String = ""
    
    var body: some View {
        VStack {
            
            TextField("キーワード", text: $inputText, prompt: Text("キーワードを入力してください"))
                .onSubmit {
                    dispSearchKey = inputText
                }
                .padding()
            MapView(searchKey: dispSearchKey)
        }
    }
}

ContentView.swift

struct ContentView: View {
    
    @State var inputText: String = ""
    @State var dispSearchKey: String = ""
    @State var dispMapType: MKMapType = .standard
    
    var body: some View {
        VStack {
            
            TextField("キーワード", text: $inputText, prompt: Text("キーワードを入力してください"))
                .onSubmit {
                    dispSearchKey = inputText
                }
                .padding()
            
            ZStack(alignment: .bottomTrailing){
                
                MapView(searchKey: dispSearchKey, mapType: dispMapType)
                
                Button(action: {
                    if dispMapType == .standard {
                        dispMapType = .satellite
                    } else if dispMapType == .satellite {
                        dispMapType = .hybrid
                    } else if dispMapType == .hybrid {
                        dispMapType = .hybridFlyover
                    } else if dispMapType == .hybridFlyover {
                        dispMapType = .mutedStandard
                    } else {
                        dispMapType = .standard
                    }
                }) {
                    Image(systemName: "map")
                        .resizable()
                        .frame(width: 35.0, height: 35.0, alignment: .leading)
                }
                .padding(.trailing, 20.0)
                .padding(.bottom, 30.0)
            }
            
        }
    }
}

うーむ、これは凄いわ

[swift] マップ検索アプリでMapKitとクロージャを学習1

検索窓(TextField)、マップを表示するView(MapKit)を設置する
キーワードから緯度経度を検索 -> 緯度経度からピンの画面パーツ生成 -> ピンの画面パーツを地図画面に貼り付け
MapKitにはUIViewRepresentableを使用する
TextFieldでの文字入力と入力完了後(onCommit)の使い方を学ぶ
ContentView.swiftとMapView.swiftを作成する

SwiftUIのMapView.swiftを作成
アプリでMapを表示するにはMapKitをインポートする
MapKitは地図、衛星画像、ピン配置、住所から座標検索ができる

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    func makeUIView(context: Context) -> MKMapView {
        MKMapView()
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context){
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}

UIViewRepresentableを記載すると、makeUIView(viewの作成)とupdateUIView(viewの変更、再描画)が必要になる
UIViewRepresentableは、MKMapViewを使うためのラッパー

### 検索キーワードの設定
MapView.swift

struct MapView: UIViewRepresentable {
    
    let searchKey: String
    
    func makeUIView(context: Context) -> MKMapView {
        MKMapView()
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context){
        
        print(searchKey)
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView(searchKey: "東京タワー")
    }
}

ContentView.swift

struct ContentView: View {
    var body: some View {
        VStack {
            MapView(searchKey: "東京タワー")
        }
    }
}

-> シュミレーターを立ち上げると”東京タワー”と表示される

シミュレータでプログラムが実行される順番
 MyMapApp.swift -> ContentView.swift -> MapView.swift

### 検索キーワードから緯度経度を検索

    func updateUIView(_ uiView: MKMapView, context: Context){
        
        print(searchKey)
        
        let geocoder = CLGeocoder()
        geocoder.geocodeAddressString(
        searchKey ,
        completionHandler: { (placemarks, error) in
            
        if let unwrapPlacemarks = placemarks,
           let firstPlacemark = unwrapPlacemarks.first ,
           let location = firstPlacemark.location {
            
            let targetCoordinate = location.coordinate
            print(targetCoordinate)
            }
        })
    }

追加

            let pin = MKPointAnnotation()
            pin.coordinate = targetCoordinate
            pin.title = searchKey
            uiView.region = MKCoordinateRegion(
                center: targetCoordinate,
                latitudinalMeters: 500.0,
                longitudinalMeters: 500.0)

MKPointAnnotationはピンを置くための機能

地図の扱いは特殊ですね

[swift] viewの共通化

NewFile -> iOS SwiftUI view
BackgroundView.swiftで作成する

struct BackgroundView: View {
    
    let imageName: String
    
    var body: some View {
        Image(imageName)
            .resizable()
            .ignoresSafeArea()
            .aspectRatio(contentMode: .fill)
    }
}
struct BackgroundView_Previews: PreviewProvider {
    static var previews: some View {
        BackgroundView(imageName: "background")
    }
}

ContentView.swift

BackgroundView(imageName: "background")

テンプレート化もできるので
OK
早くDBまで行きたい

[swift] 楽器アプリの作成

HStackは横方向にViewをレイアウトする
Assets.xcassets に画像を配置する

### 背景画像の配置

    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea()
                .aspectRatio(contentMode: .fill)
        }
    }

HStackで配置

        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea()
                .aspectRatio(contentMode: .fill)
            HStack {
                Button(action: {}){
                    Image("cymbal")
                }
                Button(action: {}){
                    Image("guitar")
                }
            }
        }
    }

### mp3ファイルの取り込み
音源ファイルを読み込み、タップされたら音源を鳴らす
mp3をAssets.xcassetsに配置する
TargetMembershipがMyMusicになっている

### SoundPlayer.swifを追加
New Fileで Cocoa Touch Class を追加する
SoundPlayer: NSObject を追加する

iOSで音を鳴らすにはAVFoundationを読み込む
AVAudioPlayer は do catchで書く

import UIKit
import AVFoundation

class SoundPlayer: NSObject {
    let cymbalData = NSDataAsset(name: "cymbalSound")!.data
    var cymbalPlayer: AVAudioPlayer!
    
    func cymbalPlay() {
        do {
            cymbalPlayer = try AVAudioPlayer(data: cymbalData)
            cymbalPlayer.play()
        } catch {
            print("シンバルでエラーが発生しました!")
        }
    }
}

ContentView.swiftで呼び込む

struct ContentView: View {
    
    let soundPlayer = SoundPlayer()
    
    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea()
                .aspectRatio(contentMode: .fill)
            HStack {
                Button(action: {
                    soundPlayer.cymbalPlay()
                }){
                    Image("cymbal")
                }
                Button(action: {}){
                    Image("guitar")
                }
            }
        }
    }
}

### 同じようにguitarも追加
SoundPlayer.swift

class SoundPlayer: NSObject {
    let cymbalData = NSDataAsset(name: "cymbalSound")!.data
    var cymbalPlayer: AVAudioPlayer!
    
    let guitarData = NSDataAsset(name: "guitarSound")!.data
    var guitarPlayer: AVAudioPlayer!
    
    func cymbalPlay() {
        do {
            cymbalPlayer = try AVAudioPlayer(data: cymbalData)
            cymbalPlayer.play()
        } catch {
            print("シンバルでエラーが発生しました!")
        }
    }
    
    func guitarPlay() {
        do {
            guitarPlayer = try AVAudioPlayer(data: guitarData)
            guitarPlayer.play()
        } catch {
            print("ギターでエラーが発生しました!")
        }
    }
}

ContentView.swiftはcymbalと同じ

プログラミングの原理原則は他の言語と同じだから、コツを掴めばいけそうやな