他の誰かのクラスをCodableに準拠させる
Making someone else’s class conform to Codable
私たちのアプリではMKPointAnnotation、ユーザーが訪れたい興味深い場所を保存するために使用しており、iOSストレージを使用して永続的に保存したいと考えています。MKPointAnnotation-Codable.swiftという新しいSwiftファイルを作成し、MapKitのインポートを追加して、次のコードを指定します。
MKPointAnnotationのサブクラスを作成して実装Codableし、使用されているMKPointAnnotation知識からを効果的に保護できますCodable。これが現在のクラスなので、サブクラスをに準拠させることができCodableます。
↑の前に、以下にて解決!!
newLocation.title = "Example location" //追加 newLocation.subtitle = "Example subtitle" newLocation.coordinate = self.centerCoordinate
// // MKPointAnnotation-Codable.swift // BucketList // import Foundation import MapKit class CodableMKPointAnnotation: MKPointAnnotation, Codable { enum CodingKeys: CodingKey { case title, subtitle, latitude, longitude } override init() { super.init() } public required init(from decoder: Decoder) throws { super.init() let container = try decoder.container(keyedBy: CodingKeys.self) title = try container.decode(String.self, forKey: .title) subtitle = try container.decode(String.self, forKey: .subtitle) let latitude = try container.decode(CLLocationDegrees.self, forKey: .latitude) let longitude = try container.decode(CLLocationDegrees.self, forKey: .longitude) coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(title, forKey: .title) try container.encode(subtitle, forKey: .subtitle) try container.encode(coordinate.latitude, forKey: .latitude) try container.encode(coordinate.longitude, forKey: .longitude) } }
import SwiftUI import MapKit struct ContentView: View { //中心座標 @State private var centerCoordinate = CLLocationCoordinate2D() //Annotation配列 //@State private var locations = [MKPointAnnotation]() @State private var locations = [CodableMKPointAnnotation]() //plus(+)button押下 -->> sheet表示用 //mapviewのボタン(calloutAccessoryControlTapped)押下のAnnotationを特定 @State private var selectedPlace: MKPointAnnotation? //show Alert @State private var showingPlaceDetails = false //show sheet @State private var showingEditScreen = false //アプリのドキュメントディレクトリを見つける func getDocumentsDirectory() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return paths[0] } //------------- func loadData() { let filename = getDocumentsDirectory().appendingPathComponent("SavedPlaces") do { let data = try Data(contentsOf: filename) locations = try JSONDecoder().decode([CodableMKPointAnnotation].self, from: data) } catch { print("Unable to load saved data.") } } //------------- func saveData() { do { let filename = getDocumentsDirectory().appendingPathComponent("SavedPlaces") let data = try JSONEncoder().encode(self.locations) try data.write(to: filename, options: [.atomicWrite, .completeFileProtection]) } catch { print("Unable to save data.") } } var body: some View { ZStack { //引数として、中心座標、 MapView(centerCoordinate: $centerCoordinate, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails, annotations: locations) .edgesIgnoringSafeArea(.all) Circle() .fill(Color.blue) .opacity(0.3) .frame(width: 32, height: 32) VStack { Spacer() HStack { Spacer() Button(action: { // create a new location //let newLocation = MKPointAnnotation() let newLocation = CodableMKPointAnnotation() newLocation.title = "Example location" //追加 newLocation.subtitle = "Example subtitle" newLocation.coordinate = self.centerCoordinate self.locations.append(newLocation) self.selectedPlace = newLocation self.showingEditScreen = true }) { Image(systemName: "plus") } .padding() .background(Color.black.opacity(0.75)) .foregroundColor(.white) .font(.title) .clipShape(Circle()) .padding(.trailing) } } } .alert(isPresented: $showingPlaceDetails) { Alert(title: Text(selectedPlace?.title ?? "Unknown"), message: Text(selectedPlace?.subtitle ?? "Missing place information."), primaryButton: .default(Text("OK")), secondaryButton: .default(Text("Edit")) { // edit this place self.showingEditScreen = true }) } .sheet(isPresented: $showingEditScreen, onDismiss: saveData) { if self.selectedPlace != nil { EditView(placemark: self.selectedPlace!) } } //UserDefaultsからlocations読み込み .onAppear(perform: loadData) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
Face IDの背後にあるUIをロックする
Locking our UI behind Face ID
Info.plist
we need to add the “Privacy – Face ID Usage Description” key to Info.plist, explaining to the user why we want to use Face ID.You can enter what you like, but “Please authenticate yourself to unlock your places” seems like a good choice.
// // ContentView.swift // BucketList // import SwiftUI import MapKit import LocalAuthentication struct ContentView: View { @State private var isUnlocked = false //中心座標 @State private var centerCoordinate = CLLocationCoordinate2D() //Annotation配列 //@State private var locations = [MKPointAnnotation]() @State private var locations = [CodableMKPointAnnotation]() //plus(+)button押下 -->> sheet表示用 //mapviewのボタン(calloutAccessoryControlTapped)押下のAnnotationを特定 @State private var selectedPlace: MKPointAnnotation? //show Alert @State private var showingPlaceDetails = false //show sheet @State private var showingEditScreen = false //アプリのドキュメントディレクトリを見つける func getDocumentsDirectory() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return paths[0] } //------------- func loadData() { let filename = getDocumentsDirectory().appendingPathComponent("SavedPlaces") do { let data = try Data(contentsOf: filename) locations = try JSONDecoder().decode([CodableMKPointAnnotation].self, from: data) } catch { print("Unable to load saved data.") } } //------------- func saveData() { do { let filename = getDocumentsDirectory().appendingPathComponent("SavedPlaces") let data = try JSONEncoder().encode(self.locations) try data.write(to: filename, options: [.atomicWrite, .completeFileProtection]) } catch { print("Unable to save data.") } } //------------- func authenticate() { let context = LAContext() var error: NSError? if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { let reason = "Please authenticate yourself to unlock your places." context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in DispatchQueue.main.async { if success { self.isUnlocked = true } else { // error } } } } else { // no biometrics } } var body: some View { ZStack { if isUnlocked { //引数として、中心座標、 MapView(centerCoordinate: $centerCoordinate, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails, annotations: locations) .edgesIgnoringSafeArea(.all) Circle() .fill(Color.blue) .opacity(0.3) .frame(width: 32, height: 32) VStack { Spacer() HStack { Spacer() Button(action: { // create a new location //let newLocation = MKPointAnnotation() let newLocation = CodableMKPointAnnotation() newLocation.title = "Example location" //追加 newLocation.subtitle = "Example subtitle" newLocation.coordinate = self.centerCoordinate self.locations.append(newLocation) self.selectedPlace = newLocation self.showingEditScreen = true }) { Image(systemName: "plus") } .padding() .background(Color.black.opacity(0.75)) .foregroundColor(.white) .font(.title) .clipShape(Circle()) .padding(.trailing) } } } else { // button here Button("Unlock Places") { self.authenticate() } .padding() .background(Color.blue) .foregroundColor(.white) .clipShape(Capsule()) } } .alert(isPresented: $showingPlaceDetails) { Alert(title: Text(selectedPlace?.title ?? "Unknown"), message: Text(selectedPlace?.subtitle ?? "Missing place information."), primaryButton: .default(Text("OK")), secondaryButton: .default(Text("Edit")) { // edit this place self.showingEditScreen = true }) } .sheet(isPresented: $showingEditScreen, onDismiss: saveData) { if self.selectedPlace != nil { EditView(placemark: self.selectedPlace!) } } //UserDefaultsからlocations読み込み .onAppear(perform: loadData) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
これは今までで最大のプロジェクトでしたが、Comparableカスタムタイプへの追加、ドキュメントディレクトリの検索、MapKitの統合、生体認証の使用、安全なData書き込みなど、多くのことをカバーしました。
任意の-これとInstafilterの間に、あなたは今、あなたのアプリにUIKitの任意の部分を埋め込む方法を見てきたUIViewか、UIViewController今SwiftUIの内側に配置することができます。これにより、UIKitの学習に時間を費やすことができる限り、作成できるアプリの種類が大幅に広がります。時間の経過とともに、SwiftUIはその機能を拡張して拡大しますが、現時点では、その限界を理解し、その強みを発揮することが重要です。