iOS + watchOS Tourism App(含源码可简单复现)

news2024/12/26 10:37:51

iOS-app-trip-in-HK - how to study and get the new product in 2 weeks

⚠️ 本文源码已上传到GitHub: https://github.com/boots-coder/whereWeGo

during 12. 3 - 12.17 = 14 days

ps:本人有java springboot开发和python的人工智能的基础知识背景; 但是我认为学习新技术的方法应该是一样的 ,本文记录了从0开始到一个产品落地的全过程

Day 1 确定需求和进行初步调研

目前github 主要针对的是iOS 单独的应用, 并没有和iwatch 进行深入的连接;因此该项目进行开发就有了其独特的意义;

旅游产品:携程等,广告多, 国内软件和应用冗余;希望开发一款比较干净的旅游产品

Some details :

以 china HK 为例子:

  • 以香港旅游为主要研究对象和主题
  • 酒店预定-询
  • 介绍酒店附近景点
  • apple 全设备支持(iOS-ipad-iwatch)
  • 预约之后需要有sql 日志输出

一些开源程序的搜索

序号项目名称描述GitHub 链接
1NearMe使用 SwiftUI 开发的 iOS 应用,可发现附近的地点,利用 MapKit 和 Core Location 提供交互式地图、搜索、详细地点信息和路线指引等功能。NearMe
2swiftui-hike-app基于 SwiftUI 的 iOS 应用,旨在激发用户发现和探索徒步旅行路线,展示了 SwiftUI 在创建精美卡片视图和优雅设置界面方面的能力。swiftui-hike-app
3Travel-Booking-App-SwiftUI电子商务旅行预订应用,用户可预订酒店和活动,使用 SwiftUI 开发,集成了 MapKit 和 Apple Pay。Travel-Booking-App-SwiftUI
4TravelApp-SwiftUI使用 SwiftUI 构建的旅行应用界面示例,展示了如何使用 SwiftUI 构建现代旅行应用界面。TravelApp-SwiftUI
5swift_travel开源的公共交通应用,提供路线搜索、时刻表和 AI 驱动的智能建议等功能。swift_travel
6AwesomeSwift汇集了 Swift 开源精选资源的项目,以思维导图形式呈现,包含多个旅行相关的应用示例。AwesomeSwift
7swift-open-projectSwift 开源项目分类汇总,其中包含多个旅行应用的示例和资源。swift-open-project
8awesome-swift-1汇集了 Swift 开源精选资源的文档,以思维导图形式呈现,包含旅行应用的示例。awesome-swift-1
9awesome-swiftui精心策划的优秀 SwiftUI 开源项目列表,其中包含旅行应用的示例。awesome-swiftui
10OpenSwiftUI开源的 SwiftUI 实现项目,可用于构建跨平台的旅行应用。OpenSwiftUI

经过调研,决定在标亮度的产品上做二次开发

Day 2 ,学习和入门 swift

参考:

swift官网学习导航:

https://developer.apple.com/cn/documentation/swift/?utm_source=chatgpt.com

语法特性:

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/

swiftUI:

https://developer.apple.com/cn/xcode/swiftui/

https://developer.apple.com/tutorials/swiftui/

playGround:

https://designcode.io/swiftui-handbook-visual-editor-in-xcode

Day3 ,修改项目代码

  1. 形成详细的源文件的技术报告

该开源项目“Travel-Booking-App-SwiftUI”是一个用于预订酒店和活动的电子商务旅行应用,采用 Swift 和 SwiftUI 开发,集成了 MapKit 和 Apple Pay 等技术。

项目文件结构及各文件作用:

​ • ExploreHere.xcodeproj:Xcode 项目文件,包含项目的配置和元数据。

​ • ExploreHere:主代码目录,包含应用的源代码和资源。

​ • .DS_Store:macOS 系统文件,存储文件夹的显示属性。

​ • README.md:项目的自述文件,提供项目概述、技术栈和功能说明。

项目包含的 UI:

​ • 探索活动界面:展示可预订的活动列表,用户可以浏览并选择感兴趣的活动。

​ • 购物车界面:用户添加的旅行计划和活动会显示在此,支持添加和删除操作。

​ • 详细视图界面:提供所选活动或酒店的详细信息,包括描述、位置等。

​ • 旅行计划视图:展示用户的旅行行程和相关预订信息。

使用的技术:

​ • Swift:主要编程语言。

​ • SwiftUI:用于构建用户界面的声明式框架。

​ • MapKit:用于在应用中嵌入地图功能,显示活动或酒店的位置。

​ • Apple Pay:集成支付功能,方便用户完成预订付款。

实现的主要功能:

​ • 活动和酒店浏览:用户可以浏览可预订的活动和酒店列表,查看详细信息。

​ • 添加和管理旅行计划:用户可以将感兴趣的活动或酒店添加到购物车,管理自己的旅行计划。

​ • 地图显示:通过 MapKit 显示活动或酒店的位置,帮助用户直观了解地理信息。

​ • 支付功能:集成 Apple Pay,用户可以直接在应用内完成支付,简化预订流程。

该项目采用 MVVM(Model-View-ViewModel)架构,确保代码的可维护性和可扩展性。

Day 4 转代码到 iwatch

生成图标:https://www.appicon.co/

核心问题: 数据一致性

下面是一个总体思路和示例,帮助你在 watchOS 与 iOS 间共享酒店数据,并在 watchOS 上预订酒店后,将结果同步到 iOS 的购物车中。

如果你需要更多特定信息(比如 WatchConnectivity 的详细实现、数据模型如何保持一致、或是工程文件的组织方式),请告诉我你需要哪些进一步信息,我将为你提供更详细的说明。


总体思路
  1. 数据一致性
    在 watchOS 与 iOS 间共享同一套酒店数据(Hotels 结构、hotelList数组等),确保 watchOS 和 iOS 使用相同的数据模型文件。在实际项目中,你可以把 Hotels.swiftHotelType.swift 等数据层文件放入一个 Shared 文件夹,并通过 Target Membership 同时勾选 iOS 和 watchOS,这样两端共享同一套代码。

  2. 数据同步机制
    使用 WatchConnectivity 框架来在 iOS 和 watchOS 间传递数据。当用户在 watchOS 的 DetailView 中点击注册(预订)按钮时,向 iPhone 发送一条消息包含预订酒店的信息。iPhone 端接收到该信息后,将该酒店加入 HotelType.hotelComponent,从而更新 iOS 的购物车界面。

  3. 具体步骤

    • 在 iOS 和 watchOS 的 Extension 中分别创建 WCSession 会话,并设置代理。
    • 当 watchOS 用户点击 “Register” 按钮时,通过 session.sendMessage 发送 ["action": "addHotel", "hotelID": ...] 或直接发送完整的酒店信息字典。
    • iOS 接收到后根据 ID 或信息匹配到对应的酒店对象,调用 hotelsList.addHotels(newItem:) 将酒店加入购物车。
    • iOS 上的购物车界面因为数据是 @Published,会自动刷新显示新加入的酒店。

Day 5 完善项目和进行成果展示

  • 修复数据一致性问题
  • 添加图片

下面是本次实现 Apple Watch 与 iOS App 之间数据同步功能的完整工作记录和技术总结,可作为其他开发者的参考与学习资料。


背景与目标

我们希望在 Apple Watch 上完成酒店预订操作后,将该预订信息实时同步到 iOS 端的购物车界面中。通过实现此功能,用户可以在 Apple Watch 上独立浏览并订购酒店,而 iOS App 则能自动更新购物车数据,呈现最新的订单状态。

使用技术与框架

  • WatchConnectivity:Apple 提供的框架,用于在可配对的 iPhone 与 Apple Watch 之间进行双向通信。
  • SwiftUI:构建 iOS 与 watchOS App 的 UI 界面,并使用 @EnvironmentObject@Published 属性实现数据驱动的界面更新。

实现步骤

一、项目结构与初始化
  1. 同一 Workspace 中的多 Target
    确保项目中包含 iOS App 目标和 watchOS Extension/Watch App 目标。这使得 iOS 和 watchOS 项目可以共享数据模型文件(如 HotelsHotelType),并在后续使用 WatchConnectivity 进行通信。
  2. 单独运行测试
    在实现数据同步前,先确保 iOS 与 watchOS 应用均能独立运行无误。(这一步建议分开启动,这样有利于进行调试)
二、启动与激活 WatchConnectivity 会话
  1. iOS 端会话初始化
    在 iOS App 中(App入口点,如 ExploreHereApp.swift),初始化 iOSAppSessionManager 单例,激活 WCSession

    @main
    struct ExploreHereApp: App {
        init() {
            let _ = iOSAppSessionManager.shared
        }
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .environmentObject(TripType())
                    .environmentObject(HotelType.shared) // 单例注入
            }
        }
    }
    

    iOSAppSessionManager.swift

    swift
    
    import WatchConnectivity
    
    class iOSAppSessionManager: NSObject, WCSessionDelegate {
        static let shared = iOSAppSessionManager()
        let session = WCSession.default
    
        override init() {
            super.init()
            if WCSession.isSupported() {
                session.delegate = self
                session.activate()
            }
        }
    
        func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
            print("iOS: WCSession activationDidComplete: \(activationState) error: \(String(describing: error))")
        }
    }
    
  2. watchOS 端会话初始化
    在 watchOS App 的入口点中,同样创建 WatchSessionManager 单例初始化会话。

    @main
    struct ExploreHereWatchApp: App {
        init() {
            let _ = WatchSessionManager.shared
        }
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .environmentObject(HotelType.shared)
            }
        }
    }
    

    WatchSessionManager.swift

    import WatchConnectivity
    
    class WatchSessionManager: NSObject, WCSessionDelegate, ObservableObject {
        static let shared = WatchSessionManager()
        let session = WCSession.default
    
        override init() {
            super.init()
            if WCSession.isSupported() {
                session.delegate = self
                session.activate()
            }
        }
    
        func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
            print("watchOS: WCSession activationDidComplete: \(activationState) error: \(String(describing: error))")
        }
    
        func sessionReachabilityDidChange(_ session: WCSession) {
            print("watchOS: Reachability Changed: \(session.isReachable)")
        }
    }
    

通过上述步骤,iOS 和 watchOS 端的会话被激活,当两个设备连接时 session.isReachable 会为 true,表明可以双向通信。

三、数据模型与单例共享(这一步非常关键)
  1. 数据模型定义

    struct Hotels: Identifiable {
        var id = UUID()
        var name: String
        var description: String
        var image: String
        var suitRoom: String?
        var price: Int
        var nearbyAttractions: [String]
        var latitude: Double
        var longitude: Double
    }
    
    class HotelType: ObservableObject {
        static let shared = HotelType() // 单例
    
        @Published var hotelComponent: [Hotels] = []
    
        func addHotels(newItem: Hotels) {
            hotelComponent.append(newItem)
            print("HotelType: Added hotel - \(newItem.name)")
        }
    
        func removeHotel(item: Hotels) {
            if let index = hotelComponent.firstIndex(where: { $0.id == item.id }) {
                hotelComponent.remove(at: index)
            }
        }
    }
    

    在 iOS 与 watchOS 两端共享同一数据模型文件,以保持数据一致性与便于转换。

  2. 环境对象注入
    在 iOS 的 ExploreHereApp 中使用 .environmentObject(HotelType.shared) 以保证全局使用相同的 HotelType 实例,使得更新在任意位置发生后,UI 都能感知到变更。

四、从 watchOS 端发送数据到 iOS 端
  1. watchOS 发送数据
    当用户在 watchOS 上点击 “Register” 按钮预订酒店时,构建可序列化的字典并通过 sendMessage 发送到 iOS:

    extension WatchSessionManager {
        func sendHotelToiPhone(hotel: Hotels) {
            guard session.isReachable else {
                print("watchOS: iPhone不可达,数据未发送")
                return
            }
    
            var hotelData: [String: Any] = [
                "name": hotel.name,
                "description": hotel.description,
                "image": hotel.image,
                "price": hotel.price,
                "nearbyAttractions": hotel.nearbyAttractions,
                "latitude": hotel.latitude,
                "longitude": hotel.longitude
            ]
    
            // 若 suitRoom 存在则添加,否则省略或使用空字符串避免 nil
            if let room = hotel.suitRoom {
                hotelData["suitRoom"] = room
            }
    
            session.sendMessage(["hotel": hotelData], replyHandler: nil, errorHandler: { error in
                print("watchOS: 发送失败 - \(error.localizedDescription)")
            })
        }
    }
    

    在 watchOS DetailView 的注册按钮中调用:

    
    Button(action: {
        // 可选,本地添加到 watchOS 端的数据模型
        // hotelsList.addHotels(newItem: hotel)
    
        // 发送给 iOS
        WatchSessionManager.shared.sendHotelToiPhone(hotel: hotel)
    }) {
        Text("Register").foregroundColor(.white)
            .padding()
            .background(Color.blue)
            .cornerRadius(15)
    }
    
  2. iOS 接收数据并更新模型
    iOSAppSessionManager 中实现 didReceiveMessage,解析收到的酒店字典并添加到 HotelType.shared

    
    extension iOSAppSessionManager {
        func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
            print("iOS: didReceiveMessage triggered")
            if let hotelData = message["hotel"] as? [String:Any],
               let h = decodeHotel(from: hotelData) {
                DispatchQueue.main.async {
                    HotelType.shared.addHotels(newItem: h)
                }
            }
        }
    
        private func decodeHotel(from dict: [String:Any]) -> Hotels? {
            guard let name = dict["name"] as? String,
                  let description = dict["description"] as? String,
                  let image = dict["image"] as? String,
                  let price = dict["price"] as? Int,
                  let nearbyAttractions = dict["nearbyAttractions"] as? [String],
                  let latitude = dict["latitude"] as? Double,
                  let longitude = dict["longitude"] as? Double else {
                return nil
            }
            let suitRoom = dict["suitRoom"] as? String
    
            return Hotels(
                name: name,
                description: description,
                image: image,
                suitRoom: suitRoom,
                price: price,
                nearbyAttractions: nearbyAttractions,
                latitude: latitude,
                longitude: longitude
            )
        }
    }
    
五、UI自动刷新与故障排查
  1. UI自动刷新
    CartView 使用 @EnvironmentObject var hotelType: HotelType,当 hotelComponent 改变时,视图会自动刷新显示最新的酒店订单。

    if hotelType.hotelComponent.count > 0 {
        ForEach(hotelType.hotelComponent, id: \.id) { hotel in
            // 显示已预订的酒店信息
        }
    } else {
        Text("No hotels booked yet!")
            .padding()
    }
    
  2. 错误与警告处理

    • 如果出现 Payload contains unsupported type 错误,检查发送的字典中是否有 nil 或非 Property List 类型的值。例如:确保可选值处理得当。(细节)
    • 若出现 “Modifying state during view update” 警告,可确保在主线程中异步更新数据,并检查在视图刷新周期外进行数据修改。(不重要)
    • 确保 iOS App 在前台才能收到 sendMessage 消息,否则考虑使用 transferUserInfo 进行异步数据传输。(没有尝试)
六、最终效果

在完成上述工作后,流程为:

  1. 用户在 Apple Watch 的 DetailView 中点击 “Register” 按钮。
  2. watchOS 通过 WatchSessionManager 使用 sendMessage 将酒店数据字典发送给 iOS。
  3. iOS iOSAppSessionManagerdidReceiveMessage 回调触发,解析数据后更新 HotelType.shared
  4. 因为 CartView 使用了 hotelType@EnvironmentObject,数据改变会自动触发 UI 刷新,并在购物车中显示新增的酒店订单。

总结

通过 WatchConnectivity 框架和 SwiftUI 的数据驱动机制,我们成功实现了 Apple Watch 与 iOS App 之间的实时数据同步。当用户在 watchOS 上进行酒店预订时,无需手动刷新或额外交互,iOS 端的购物车界面就能自动展示最新订单信息。这为跨设备的使用体验提供了便利,也为后续拓展更多交互打下了基础。

本次实现的要点包括:

  • 使用 WCSession 在 iOS 与 watchOS 之间建立通信通道。
  • 在 watchOS 上通过 sendMessage 发送 Property List 类型的字典数据。
  • 在 iOS 上使用 didReceiveMessage 回调解析并更新数据模型。
  • 使用单例模式与 @EnvironmentObject 确保数据共享与 UI 同步。
  • 处理可选值与数据类型,确保消息传输过程中不出现不支持的类型。

这样,其他开发者可以参考本记录快速上手,将类似的数据同步功能集成到自己的 watchOS 和 iOS 应用中。

补充

  1. iOS 端成功接收到来自 watchOS 的消息,但 iOS 端的购物车界面(CartView)没有更新
问题分析
  1. 实例不一致
    • 你的 CartView 使用的是通过 @EnvironmentObject var hotelType: HotelType 注入的 hotelType 实例。
    • 而在 iOSAppSessionManager 中,你试图通过 HotelType.shared 来更新数据。
    • 如果 HotelType 没有定义 shared 静态实例,或者 shared 实例与 CartView 使用的实例不同步,CartView 自然无法感知到数据的变化。
解决方案
步骤一:确保 HotelType 使用单例模式

为了确保所有地方都在更新同一个 HotelType 实例,我们将 HotelType 修改为单例模式,并确保在 ExploreHereApp 中注入的是这个单例实例。

  1. 修改 HotelType

    import Foundation
    import SwiftUI
    
    //MARK: MODEL VIEW
    
    class HotelType: ObservableObject {
        static let shared = HotelType() // 添加这一行,创建单例实例
    
        @Published var hotelComponent: [Hotels] = []
    
        func addHotels(newItem: Hotels) {
            hotelComponent.append(newItem)
        }
    
        // 新增删除方法
        func removeHotel(item: Hotels) {
            if let index = hotelComponent.firstIndex(where: { $0.id == item.id }) {
                hotelComponent.remove(at: index)
            }
        }
    }
    
  2. 修改 ExploreHereApp 以使用 HotelType.shared

    import SwiftUI
    
    @main
    struct ExploreHereApp: App {
        @StateObject private var tripType = TripType()
        // 移除 @StateObject private var hotelType = HotelType()
    
        init() {
            let _ = iOSAppSessionManager.shared // 确保 iOS 端的会话管理器被初始化
        }
    
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .environmentObject(tripType)
                    .environmentObject(HotelType.shared) // 使用单例实例
            }
        }
    }
    
  3. 确保 iOSAppSessionManager 使用同一个 HotelType 实例

    import Foundation
    import WatchConnectivity
    
    class iOSAppSessionManager: NSObject, WCSessionDelegate {
        static let shared = iOSAppSessionManager()
        let session = WCSession.default
    
        override init() {
            super.init()
            if WCSession.isSupported() {
                session.delegate = self
                session.activate()
            }
        }
    
        func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
            // 会话激活后会调用此处
            print("iOS: WCSession activationDidComplete: \(activationState) error: \(String(describing: error))")
        }
    
        // 移除不可用的方法
        // func sessionDidBecomeInactive(_ session: WCSession) {}
        // func sessionDidDeactivate(_ session: WCSession) {}
    }
    
    extension iOSAppSessionManager {
        func session(_ session: WCSession, didReceiveMessage message: [String : Any]) {
            // 当 watchOS 端发来酒店数据时处理
            print("iOS: didReceiveMessage triggered")
            if let hotelData = message["hotel"] as? [String:Any],
               let h = decodeHotel(from: hotelData) {
                DispatchQueue.main.async {
                    HotelType.shared.addHotels(newItem: h)
                }
            }
        }
    
        private func decodeHotel(from dict: [String:Any]) -> Hotels? {
            guard let name = dict["name"] as? String,
                  let description = dict["description"] as? String,
                  let image = dict["image"] as? String,
                  let price = dict["price"] as? Int,
                  let nearbyAttractions = dict["nearbyAttractions"] as? [String],
                  let latitude = dict["latitude"] as? Double,
                  let longitude = dict["longitude"] as? Double else {
                return nil
            }
    
            let suitRoom = dict["suitRoom"] as? String
            return Hotels(
                name: name,
                description: description,
                image: image,
                suitRoom: suitRoom,
                price: price,
                nearbyAttractions: nearbyAttractions,
                latitude: latitude,
                longitude: longitude
            )
        }
    }
    
步骤二:确保 HotelType.shared 被正确注入
  1. 检查 ContentView

    确保 ContentView 使用的是通过 @EnvironmentObject 注入的 HotelType.shared 实例。

    
    import SwiftUI
    
    struct ContentView: View {
        @EnvironmentObject var tripType : TripType
        @EnvironmentObject var hotelType : HotelType
        @State private var ListScreenIsShowing = false
        @State private var selection = 1
    
        var body: some View {
            VStack{
                TabView(selection:$selection){
    
                    Homescreen()
                        .tabItem {
                            Image(systemName: "house")
                            Text("Home")
                                .fontWeight(.black)
                        }
                        .tag(3)
                    ListScreen(ListScreenisShowing: $ListScreenIsShowing)
                        .tabItem{
                            Image(systemName: "bed.double")
                                .resizable()
                            Text("Hotel")
                        }
                        .tag(0)
                    PlanTripView()
                        //.environmentObject(tripType)
    
                        .tabItem {
                            Image(systemName:"figure.walk")
                                .resizable()
                            Text ("Activities")
                        }
                        .tag(1)
                    CartView()
    
                        .tabItem{ 
                            CartButton(numberOfItems: 0)
                            Text ("Cart")
                        }
                        .badge(hotelType.hotelComponent.count)
                }
                .accentColor(.blue)
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
                .environmentObject(TripType())
                .environmentObject(HotelType.shared) // 使用单例实例
        }
    }
    
  2. 确保 HotelType.shared 被正确使用

    现在,所有通过 @EnvironmentObject var hotelType: HotelType 获取的 hotelType 实例都指向 HotelType.shared,确保数据一致。

步骤三:修复警告“Modifying state during view update”

这类警告通常是由于在 SwiftUI 视图更新期间修改了 @Published 属性。虽然你已经在主线程异步更新数据,但警告仍然存在,可能是由于视图更新与数据更新的时间点冲突。

解决方法

  1. 确保数据更新在视图更新之外

    你的 iOSAppSessionManager 已经通过 DispatchQueue.main.async 来异步更新数据,这是正确的。确保没有其他地方在视图更新期间同步修改 hotelType.hotelComponent

  2. 避免在视图生命周期方法中直接修改数据

    确保你没有在视图的 body 或其他同步执行的闭包中直接修改 hotelType.hotelComponent

  3. 添加更多调试信息

    HotelType.addHotels(newItem:) 中添加打印语句,确认数据确实被添加。

    class HotelType: ObservableObject {
        static let shared = HotelType()
    
        @Published var hotelComponent: [Hotels] = []
    
        func addHotels(newItem: Hotels) {
            hotelComponent.append(newItem)
            print("HotelType: Added hotel - \(newItem.name)")
        }
    
        func removeHotel(item: Hotels) {
            if let index = hotelComponent.firstIndex(where: { $0.id == item.id }) {
                hotelComponent.remove(at: index)
                print("HotelType: Removed hotel - \(item.name)")
            }
        }
    }
    
  4. 确认 CartView 正在监听 hotelComponent 的变化

    确保 CartView 中的 hotelType.hotelComponent 确实被观察,并在变化时刷新视图。

步骤四:验证数据同步
  1. 重新编译并运行应用

    • 先运行 iOS App: 在 iOS 模拟器或真机上运行你的 iOS App,并确保它处于前台。
    • 然后运行 watchOS App: 在 watch 模拟器或真机上运行 watchOS App。
    • 在 watchOS App 中点击“Register”按钮: 这将发送酒店数据到 iOS App。
  2. 观察控制台日志

    • iOS 端

      • 应该看到 iOS: didReceiveMessage triggered
      • 应该看到 HotelType: Added hotel - [酒店名称]
    • watchOS 端

      • 应该看到 watchOS: WCSession activationDidComplete: activated ...
      • 应该看到 watchOS: Reachability Changed: true
      • 不应该再看到 watchOS: iPhone不可达,数据未发送
  3. 检查 CartView

    • 确认 CartView 中的酒店列表是否已更新,显示了从 watchOS 添加的新酒店。
步骤五:处理“Modifying state during view update”警告

如果警告依然存在,但数据同步功能正常,你可以暂时忽略这个警告。但为了更彻底地解决,可以尝试以下方法:

  1. 延迟数据更新:(这一步没用)

    iOSAppSessionManager 中,稍微延迟数据的添加,以避免在 SwiftUI 视图更新期间修改数据。

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        HotelType.shared.addHotels(newItem: h)
    }
    
  2. 检查视图层级

    确保没有其他视图在数据更新期间触发状态变化,导致数据被多次修改。

  3. 优化数据更新逻辑

    确保数据更新仅在必要时进行,避免重复或不必要的修改。

总结

通过以下步骤,你应该能够解决数据同步和警告问题:

  1. 确保 HotelType 使用单例模式,并在 ExploreHereApp 中注入 HotelType.shared
  2. 确保 iOSAppSessionManager 使用同一个 HotelType.shared 实例 来更新数据。
  3. 确认数据更新在主线程异步执行,避免在视图更新期间修改数据。
  4. 重新运行应用,确保数据同步功能正常,CartView 能够实时显示新增的酒店数据。
  5. 处理警告信息,确保 SwiftUI 视图的稳定性和数据一致性。

展示

见github
https://github.com/boots-coder/whereWeGo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2265790.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HarmonyOS NEXT 实战之元服务:静态案例效果--航空出行

背景: 前几篇学习了元服务,后面几期就让我们开发简单的元服务吧,里面丰富的内容大家自己加,本期案例 仅供参考 先上本期效果图 ,里面图片自行替换 效果图1完整代码案例如下: import { authentication } …

WebRTC搭建与应用(五)-Coturn踩坑记

WebRTC搭建与应用(五)-Coturn踩坑记 近期由于项目需要在研究前端WebGL渲染转为云渲染,借此机会对WebRTC等有了初步了解,在此记录一下,以防遗忘。 第五章 WebRTC搭建与应用(五)-Coturn踩坑记 文章目录 WebRTC搭建与应用(五)-Coturn踩坑记前…

STM32-笔记14-排队控制系统

一、项目需求 1. 红外传感器检测有人通过并计数; 2. 计数值显示在LCD1602 3. 允许通过时,LED1闪烁,蜂鸣器不响,继电器不闭合; 4. 不允许通过时,LED2闪烁,蜂鸣器响,继电器闭合&#…

【QT开发自制小工具】PDF/图片转excel---调用百度OCR API接口

前言 前几年WPS还可以免费处理5页以内的PDF转excel,现在必须付费了,而且百度其他在线的PDF转excel都是要收费的,刚好前几年调研过百度OCR的高精度含位置接口,依然是每天可以免费调用50次,本篇是基于此接口,…

【机器学习】机器学习的基本分类-半监督学习(Semi-supervised Learning)

半监督学习是一种介于监督学习和无监督学习之间的机器学习方法。它利用少量的标注数据(有监督数据)和大量的未标注数据(无监督数据)来进行模型训练,从而在标注数据不足的情况下,提升模型的性能。 半监督学习…

大模型讲师叶梓分享前沿论文:ChatDoctor——基于大模型的医疗聊天机器人

人工智能咨询培训老师叶梓 转载标明出处 人工智能讲师培训咨询老师叶梓分享前沿技术:基于大模型的医疗聊天机器人 大模型在医疗领域的应用仍相对有限,通用领域模型在提供医疗建议时常常出现错误。为了解决这一问题,Li等人提出了一个名为ChatD…

GitLab 停止中国区用户访问,为用户提供60天的迁移期

近日,全球知名的代码托管平台 GitLab 宣布了一个重大变化:将停止为中国大陆、香港及澳门地区的用户提供访问服务,建议用户访问授权国内的产品极狐 GitLab.cn。 极狐 GitLab.cn 是 GitLab 授权的独立中国公司,之前该公司还发生过举…

H3C MPLS跨域optionB

实验拓扑 实验需求 如图,VPN1 和 VPN2 分别通过运营商 MPLS VPN 连接各自分支机构按照图示配置 IP 地址,VPN1 和 VPN2 连接同一个 PE 设备的私网 IP 网段存在地址复用,使用多 VRF 技术来防止 IP 冲突AS 100 和 AS 200 内部的公共网络中各自运行 OSPF 使 AS 内各设备的 Loo…

Flink SQL Cookbook on Zeppelin 部署使用

简介:对于初学者来说,学习 Flink 可能不是一件容易的事情。看文档是一种学习,更重要的是实践起来。但对于一个初学者来说要把一个 Flink SQL 跑起来还真不容易,要搭各种环境,真心累。很幸运的是,Flink 生态…

6、mysql的MHA故障切换

MHA的含义 MHA:master high availability,建立在主从复制基础上的故障切换的软件系统。 主从复制的单点问题: 当主从复制当中,主服务器发生故障,会自动切换到一台从服务器,然后把从服务器升格成主&…

基于单片机的智能递口罩机器人设计

本设计是一款智能递口罩机器人,主控器采用STM32单片机,ESP32协同控制,在支持MicroPython的OpenMV机器视觉模块的控制下,实现人脸搜索与识别,进而控制小车的运动及机械臂递口罩动作。这款机器人拥有温湿度传感器&#x…

实训项目-人力资源管理系统-1Company子模块

目录 前言: 用例图设计: 系统设计 开发方式: 技术架构 系统结构: API文档: 工程搭建: 搭建父项目 pom: 创建公共子模块: 返回实体: 分布式id生成器: …

前端bug调试

报错和Bug,是贯穿程序员整个编程生涯中,无法回避的问题。而调试,就是帮助程序员定位问题、解决问题的重要手段,因此,调试是每个程序员必备技能。 调试基本流程 核心原则:最重要的就是不断地缩小范围&…

【落羽的落羽 C语言篇】自定义类型——联合体、枚举

文章目录 一、联合体1. 联合体类型的声明2. 联合体的特点3. 联合体的大小4. 联合体和结构体的对比 二、枚举1. 枚举类型的声明2. 枚举类型的优点 一、联合体 1. 联合体类型的声明 联合体像结构体一样,也是由一个或多个成员构成,这些成员可以是不同的类…

大数据技术-Hadoop(二)HDFS的介绍与使用

目录 1、HDFS简介 1.1 什么是HDFS 1.2 HDFS的优点 1.3、HDFS的架构 1.3.1、 NameNode 1.3.2、 NameNode的职责 1.3.3、DataNode 1.3.4、 DataNode的职责 1.3.5、Secondary NameNode 1.3.6、Secondary NameNode的职责 2、HDFS的工作原理 2.1、文件存储 2.2 、数据写…

学习threejs,THREE.CircleGeometry 二维平面圆形几何体

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.CircleGeometry 圆形…

替换 Docker.io 的 Harbor 安全部署指南:域名与 IP 双支持的镜像管理解决方案

经过验证 替换 Docker.io 的方式失败了, 以下的过程中还是需要设置 registry-mirrors 才行 以下是一篇详细教程,展示如何基于 openssl.conf 配置生成域名为 registry-1.docker.io 和 IP 地址为 172.16.20.20 的证书,构建 Harbor 服务。 环境准备 系统环境…

【源码编译】windows下mingw64安装以及cmake调用

最近因为安装MIRTK库,太多第三方依赖了,太折磨了,学习了使用Cmake,有些库又需要Fortran编译器,VS2022里面装了但又调用不了,也不知道为什么,最后装的mingw64,记录一下。 1、mingw64安…

【机器学习(九)】分类和回归任务-多层感知机(Multilayer Perceptron,MLP)算法-Sentosa_DSML社区版 (1)111

文章目录 一、算法概念111二、算法原理(一)感知机(二)多层感知机1、隐藏层2、激活函数sigma函数tanh函数ReLU函数 3、反向传播算法 三、算法优缺点(一)优点(二)缺点 四、MLP分类任务…

基于AI IDE 打造快速化的游戏LUA脚本的生成系统

前面写了一篇关于使用AI IDE进行C安全开发的博客《使用AI IDE 助力 C 高性能安全开发!》, 得到许多同学们的喜欢,今天我们来继续在游戏开发中扩展一下AI的能力,看看能不能给游戏研发团队一些启发。 在游戏研发中,Lua曾…