Приложение Flutter iOS Companion с ошибкой приложения Watch: во время архивирования нет такого модуля «WatchKit» ⇐ IOS
Приложение Flutter iOS Companion с ошибкой приложения Watch: во время архивирования нет такого модуля «WatchKit»
У меня есть приложение Flutter для iOS, которое связано с целью приложения для просмотра. Основная функциональность заключается в том, что текущее приложение для часов отслеживает частоту сердечных сокращений пользователя в реальном времени через приложение для часов.
Проблема: Во время отладки я могу скомпилировать приложение, и оно работает нормально с обеих сторон, но когда я захотел заархивировать сборку, возникли эти проблемы. Не знаю, как это исправить или откуда это появляется.
Xcode: Версия 15.0 (15A240d)
iOS:
[*]Минимальное развертывание: 17,0 [*]Возможности: фоновые режимы, HealthKit [*]Идентификатор пакета: com.organisation.productName
Приложение для часов:
[*]Минимальное развертывание: 10,0 [*]Возможности: фоновые режимы, HealthKit [*]Идентификатор пакета: com.organisation.productName.watchkit
Приложения/ссылки к проблеме: Ссылка
AppDelegate:
импортировать UIKit импортировать флаттер импортировать WatchConnectivity @UIApplicationMain @objc класс AppDelegate: FlutterAppDelegate { сеанс var: WCSession? переопределить приложение func( _ приложение: UIApplication, DidFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Бул { GeneratedPluginRegistrant.register(с: self) initFlutterChannel() если WCSession.isSupported() { print("Сеанс просмотра поддерживается") сеанс = WCSession.default; сеанс?.делегат = сам; сеанс?.активировать(); } вернуть super.application(application, DidFinishLaunchingWithOptions: launchOptions) } частная функция initFlutterChannel() { если пусть контроллер = окно?.rootViewController как? Флаттервиевконтроллер { пусть канал = FlutterMethodChannel( имя: "com.org.productName", binaryMessenger: контроллер.binaryMessenger) Channel.setMethodCallHandler({ [слабое я] ( вызов: FlutterMethodCall, результат: @escaping FlutterResult) -> Пустота в переключить вызов.метод { случай «flutterToWatch»: печать("futterToWatch") Guard пусть watchSession = self?.session, watchSession.isPaired, watchSession.isReachable, пусть методData = call.arguments как? [Строка: любая], пусть метод = методДанные["метод"], пусть данные = методДанные["данные"] как? Какие-то еще { результат (ложь) возвращаться } let watchData: [String: Any] = ["метод": метод, "данные": данные] печать (смотреть данные) // Передаем полученное сообщение на Apple Watch // watchSession.sendMessage(watchData, RepHandler: nil, errorHandler: ) watchSession.sendMessage(watchData) { (replaydata) в print("\(replaydata)") } errorHandler: { (ошибка) в print("\(ошибка)") } результат (правда) по умолчанию: результат (FlutterMethodNotImplemented) } }) } } } расширение AppDelegate: WCSessionDelegate { сеанс func (_ сеанс: WCSession, активацияDidCompleteWith активацияState: WCSessionActivationState, ошибка: Ошибка?) { } func sessionDidBecomeInactive (_ сеанс: WCSession) { } func sessionDidDeactivate (_ сеанс: WCSession) { } func session (_ session: WCSession, сообщение DidReceiveMessage: [String: Any]) { DispatchQueue.main.async { если let метод = сообщение["метод"] как? Строка, пусть контроллер = self.window?.rootViewController как? Флаттервиевконтроллер { пусть канал = FlutterMethodChannel( имя: "com.org.productName", binaryMessenger: контроллер.binaryMessenger) Channel.invokeMethod(метод, аргументы: сообщение) } } } } Приложение для часов/WatchViewModel.Swift:
импортировать SwiftUI импортировать WatchConnectivity импортировать HealthKit импортировать WatchKit класс WatchViewModel: NSObject, ObservableObject, HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate { func WorkoutSession (_ WorkoutSession: HKWorkoutSession, DidChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) { print("workoutSession>>didChangeTo \(toState)") // Подождите, пока сеанс перейдет в другое состояние, прежде чем завершить построитель. если toState == .ended { builder?.endCollection(withEnd: date) { (успех, ошибка) в self.builder?.finishWorkout { (тренировка, ошибка) в } } } } func WorkoutSession (_ WorkoutSession: HKWorkoutSession, ошибка DidFailWithError: Ошибка) { print("workoutSession>>didFailWithError \(ошибка)") } // WKInterfaceController, HKWorkoutSessionDelegate, func WorkoutBuilder (_ WorkoutBuilder: HKLiveWorkoutBuilder, DidCollectDataOfcollectedTypes: Set) { для типа в CollectTypes { if type == HKQuantityType.quantityType(forIdentifier: .heartRate)! { // Обработка данных о частоте пульса если пусть статистика = тренировкаBuilder.statistics (для: введите as! HKQuantityType) { пусть heartRateUnit = HKUnit.count().unitDivided(от: HKUnit.MINUT()) пусть значение = статистика.mostRecentQuantity()?.doubleValue(for: heartRateUnit) ?? 0 счетчик = Целое (значение) sendDataMessage(для: .sendHRToFlutter, data: ["counter": counter]) print("Частота пульса во время тренировки: \(значение) ударов в минуту") // Вы можете обновить пользовательский интерфейс или выполнить другие действия с данными о частоте пульса } } // При необходимости обрабатываем другие собранные типы данных } } func WorkoutBuilderDidCollectEvent (_ WorkoutBuilder: HKLiveWorkoutBuilder) { //если тренировкаBuilder.workoutEvents.count > 0{ пусть тренировкиEvents = WorkoutBuilder.workoutEvents для события в WorkEvents { print("Work Builder собирает данные: ",event) } //} } сеанс var: WCSession пусть healthStore = HKHealthStore() вар-строитель: HKLiveWorkoutBuilder? вар workOutSession: HKWorkoutSession? пусть очередь = OperationQueue() @Published счетчик переменных = 0 // Начать тренировку. func startWorkout (workoutType: HKWorkoutActivityType) { пусть конфигурация = HKWorkoutConfiguration() Configuration.activityType = тип тренировки конфигурация.locationType = .indoor // Создаем сеанс и получаем конструктор тренировок. делать { workOutSession = попробуйте HKWorkoutSession (healthStore: healthStore, конфигурация: конфигурация) builder = workOutSession?.associatedWorkoutBuilder() строитель?.делегат = сам builder?.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, тренировкаКонфигурация: конфигурация) // Начинаем тренировку workOutSession?.startActivity(с: Date()) } ловить { // Обработка любых исключений. print("Ошибка начала тренировки: \(error.localizedDescription)") возвращаться } // Настройка сеанса и конструктора. // сеанс1?.делегат = сам строитель?.делегат = сам // Установите источник данных для построителя тренировок. builder?.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, тренировкаКонфигурация: конфигурация) // Запускаем сеанс тренировки и начинаем сбор данных. пусть startDate = Дата() workOutSession?.startActivity(с: startDate) builder?.beginCollection(withStart: startDate) { (успех, ошибка) в // Тренировка началась. print("beginCollection:", успех) } } // Добавьте больше случаев, если у вас есть больше методов получения перечисление WatchReceiveMethod: String { случай sendHRToNative } // Добавьте больше случаев, если у вас есть больше методов отправки перечисление WatchSendMethod: String { случай sendHRToFlutter } init (сессия: WCSession = .default) { self.session = сеанс // пусть wkManagerModel = WKManagerModel() супер.инит() self.session.delegate = сам сеанс.активировать() запросавторизации() } func sendDataMessage(для метода: WatchSendMethod, data: [String: Any] = [:]) { sendMessage(для: метод.rawValue, данные: данные) } // Запросить авторизацию для доступа к HealthKit. функция requestAuthorization() { // Тип количества для записи в хранилище работоспособности. пусть типыToShare: Set = [ HKQuantityType.workoutType() ] // Типы количества для чтения из хранилища работоспособности. пусть типыToRead: Set = [ HKQuantityType.quantityType(forIdentifier: .heartRate)!, HKObjectType.activitySummaryType() ] // Запрос авторизации для этих типов количества. healthStore.requestAuthorization(toShare:typesToShare, read:typesToRead) {[self] (успех, ошибка) в печать (успех) print(ошибка ?? «Нет ошибки») печать (self.dataTypesToRead()) var dataType: HKSampleType? dataType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)! print("readHealthKitData: ", self.readHealthKitData(тип: dataType!)) // Последовательная очередь для обработки образцов и вычислений. очередь.maxConcurrentOperationCount = 1 очередь.имя = "ОчередьДвиженияМенеджера" startWorkout (тип тренировки: .running) } } частная функция queryForUpdates (тип: HKObjectType) { тип переключателя { case HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!: debugPrint("HKQuantityTypeIdentifierHeartRate") по умолчанию: debugPrint("Необработанный HKObjectType: \(type)") } } частная функция dataTypesToRead() -> Set { вернуть Set(arrayLiteral: HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!, HKObjectType.workoutType() ) } /// Типы данных, которые это приложение хочет записать в HealthKit. /// /// — возвращает: набор HKSampleType. частная функция dataTypesToWrite() -> Set { вернуть Set(arrayLiteral: HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!, HKObjectType.workoutType() ) } func readHealthKitData (тип: HKSampleType) { let query = HKSampleQuery (sampleType: type, predicate: nil, limit: 1, sortDescriptors: nil) { (запрос, результаты, ошибка) в если пусть ошибка = ошибка { // Обработка ошибки запроса print("Ошибка запроса частоты пульса: \(error.localizedDescription)") возвращаться } если let heartRateSample = результаты?.сначала как? HKQuantitySample { // Доступ к значению сердечного ритма let heartRate = heartRateSample.quantity.doubleValue(for: HKUnit(from: "count/min")) // счетчик = HeartRate self.counter = Int(heartRate) self.sendDataMessage(для: .sendHRToFlutter, data: ["counter": self.counter]) print("Самосчетчик сердечного ритма: \(self.counter) BPM") // Вы можете обновить пользовательский интерфейс или выполнить другие действия с данными о частоте пульса print("Запрос сердечного ритма: \(heartRate)") } } healthStore.execute(запрос) } } расширение WatchViewModel: WCSessionDelegate { сеанс func (_ сеанс: WCSession, активацияDidCompleteWith активацияState: WCSessionActivationState, ошибка: Ошибка?) { } // Получаем сообщение от AppDelegate.swift, отправляемое с устройств iOS сеанс func (_ сеанс: WCSession, сообщение DidReceiveMessage: [String: Any],replyHandler: @escaping ([String: Any]) -> Void) { DispatchQueue.main.async { охранник пусть метод = сообщение ["метод"] как? Строка, пусть enumMethod = WatchReceiveMethod(rawValue: метод) else { возвращаться } переключить enumMethod { случай .sendHRToNative: self.counter = (сообщение["данные"] как? Int) ?? 0 } } } func sendMessage(для метода: String, data: [String: Any] = [:]) { Guard session.isReachable еще { возвращаться } let messageData: [String: Any] = ["метод": метод, "данные": данные] session.sendMessage(messageData, AnswerHandler: ноль, errorHandler: ноль) } } [*]Я пытался добавить watchconnectivity.framework в приложение-компаньон. [*]Условный импорт для WatchKit, но решение не получено, вместо этого отображается, что используемый компонент недоступен в iOS.
У меня есть приложение Flutter для iOS, которое связано с целью приложения для просмотра. Основная функциональность заключается в том, что текущее приложение для часов отслеживает частоту сердечных сокращений пользователя в реальном времени через приложение для часов.
Проблема: Во время отладки я могу скомпилировать приложение, и оно работает нормально с обеих сторон, но когда я захотел заархивировать сборку, возникли эти проблемы. Не знаю, как это исправить или откуда это появляется.
Xcode: Версия 15.0 (15A240d)
iOS:
[*]Минимальное развертывание: 17,0 [*]Возможности: фоновые режимы, HealthKit [*]Идентификатор пакета: com.organisation.productName
Приложение для часов:
[*]Минимальное развертывание: 10,0 [*]Возможности: фоновые режимы, HealthKit [*]Идентификатор пакета: com.organisation.productName.watchkit
Приложения/ссылки к проблеме: Ссылка
AppDelegate:
импортировать UIKit импортировать флаттер импортировать WatchConnectivity @UIApplicationMain @objc класс AppDelegate: FlutterAppDelegate { сеанс var: WCSession? переопределить приложение func( _ приложение: UIApplication, DidFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Бул { GeneratedPluginRegistrant.register(с: self) initFlutterChannel() если WCSession.isSupported() { print("Сеанс просмотра поддерживается") сеанс = WCSession.default; сеанс?.делегат = сам; сеанс?.активировать(); } вернуть super.application(application, DidFinishLaunchingWithOptions: launchOptions) } частная функция initFlutterChannel() { если пусть контроллер = окно?.rootViewController как? Флаттервиевконтроллер { пусть канал = FlutterMethodChannel( имя: "com.org.productName", binaryMessenger: контроллер.binaryMessenger) Channel.setMethodCallHandler({ [слабое я] ( вызов: FlutterMethodCall, результат: @escaping FlutterResult) -> Пустота в переключить вызов.метод { случай «flutterToWatch»: печать("futterToWatch") Guard пусть watchSession = self?.session, watchSession.isPaired, watchSession.isReachable, пусть методData = call.arguments как? [Строка: любая], пусть метод = методДанные["метод"], пусть данные = методДанные["данные"] как? Какие-то еще { результат (ложь) возвращаться } let watchData: [String: Any] = ["метод": метод, "данные": данные] печать (смотреть данные) // Передаем полученное сообщение на Apple Watch // watchSession.sendMessage(watchData, RepHandler: nil, errorHandler: ) watchSession.sendMessage(watchData) { (replaydata) в print("\(replaydata)") } errorHandler: { (ошибка) в print("\(ошибка)") } результат (правда) по умолчанию: результат (FlutterMethodNotImplemented) } }) } } } расширение AppDelegate: WCSessionDelegate { сеанс func (_ сеанс: WCSession, активацияDidCompleteWith активацияState: WCSessionActivationState, ошибка: Ошибка?) { } func sessionDidBecomeInactive (_ сеанс: WCSession) { } func sessionDidDeactivate (_ сеанс: WCSession) { } func session (_ session: WCSession, сообщение DidReceiveMessage: [String: Any]) { DispatchQueue.main.async { если let метод = сообщение["метод"] как? Строка, пусть контроллер = self.window?.rootViewController как? Флаттервиевконтроллер { пусть канал = FlutterMethodChannel( имя: "com.org.productName", binaryMessenger: контроллер.binaryMessenger) Channel.invokeMethod(метод, аргументы: сообщение) } } } } Приложение для часов/WatchViewModel.Swift:
импортировать SwiftUI импортировать WatchConnectivity импортировать HealthKit импортировать WatchKit класс WatchViewModel: NSObject, ObservableObject, HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate { func WorkoutSession (_ WorkoutSession: HKWorkoutSession, DidChangeTo toState: HKWorkoutSessionState, from fromState: HKWorkoutSessionState, date: Date) { print("workoutSession>>didChangeTo \(toState)") // Подождите, пока сеанс перейдет в другое состояние, прежде чем завершить построитель. если toState == .ended { builder?.endCollection(withEnd: date) { (успех, ошибка) в self.builder?.finishWorkout { (тренировка, ошибка) в } } } } func WorkoutSession (_ WorkoutSession: HKWorkoutSession, ошибка DidFailWithError: Ошибка) { print("workoutSession>>didFailWithError \(ошибка)") } // WKInterfaceController, HKWorkoutSessionDelegate, func WorkoutBuilder (_ WorkoutBuilder: HKLiveWorkoutBuilder, DidCollectDataOfcollectedTypes: Set) { для типа в CollectTypes { if type == HKQuantityType.quantityType(forIdentifier: .heartRate)! { // Обработка данных о частоте пульса если пусть статистика = тренировкаBuilder.statistics (для: введите as! HKQuantityType) { пусть heartRateUnit = HKUnit.count().unitDivided(от: HKUnit.MINUT()) пусть значение = статистика.mostRecentQuantity()?.doubleValue(for: heartRateUnit) ?? 0 счетчик = Целое (значение) sendDataMessage(для: .sendHRToFlutter, data: ["counter": counter]) print("Частота пульса во время тренировки: \(значение) ударов в минуту") // Вы можете обновить пользовательский интерфейс или выполнить другие действия с данными о частоте пульса } } // При необходимости обрабатываем другие собранные типы данных } } func WorkoutBuilderDidCollectEvent (_ WorkoutBuilder: HKLiveWorkoutBuilder) { //если тренировкаBuilder.workoutEvents.count > 0{ пусть тренировкиEvents = WorkoutBuilder.workoutEvents для события в WorkEvents { print("Work Builder собирает данные: ",event) } //} } сеанс var: WCSession пусть healthStore = HKHealthStore() вар-строитель: HKLiveWorkoutBuilder? вар workOutSession: HKWorkoutSession? пусть очередь = OperationQueue() @Published счетчик переменных = 0 // Начать тренировку. func startWorkout (workoutType: HKWorkoutActivityType) { пусть конфигурация = HKWorkoutConfiguration() Configuration.activityType = тип тренировки конфигурация.locationType = .indoor // Создаем сеанс и получаем конструктор тренировок. делать { workOutSession = попробуйте HKWorkoutSession (healthStore: healthStore, конфигурация: конфигурация) builder = workOutSession?.associatedWorkoutBuilder() строитель?.делегат = сам builder?.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, тренировкаКонфигурация: конфигурация) // Начинаем тренировку workOutSession?.startActivity(с: Date()) } ловить { // Обработка любых исключений. print("Ошибка начала тренировки: \(error.localizedDescription)") возвращаться } // Настройка сеанса и конструктора. // сеанс1?.делегат = сам строитель?.делегат = сам // Установите источник данных для построителя тренировок. builder?.dataSource = HKLiveWorkoutDataSource(healthStore: healthStore, тренировкаКонфигурация: конфигурация) // Запускаем сеанс тренировки и начинаем сбор данных. пусть startDate = Дата() workOutSession?.startActivity(с: startDate) builder?.beginCollection(withStart: startDate) { (успех, ошибка) в // Тренировка началась. print("beginCollection:", успех) } } // Добавьте больше случаев, если у вас есть больше методов получения перечисление WatchReceiveMethod: String { случай sendHRToNative } // Добавьте больше случаев, если у вас есть больше методов отправки перечисление WatchSendMethod: String { случай sendHRToFlutter } init (сессия: WCSession = .default) { self.session = сеанс // пусть wkManagerModel = WKManagerModel() супер.инит() self.session.delegate = сам сеанс.активировать() запросавторизации() } func sendDataMessage(для метода: WatchSendMethod, data: [String: Any] = [:]) { sendMessage(для: метод.rawValue, данные: данные) } // Запросить авторизацию для доступа к HealthKit. функция requestAuthorization() { // Тип количества для записи в хранилище работоспособности. пусть типыToShare: Set = [ HKQuantityType.workoutType() ] // Типы количества для чтения из хранилища работоспособности. пусть типыToRead: Set = [ HKQuantityType.quantityType(forIdentifier: .heartRate)!, HKObjectType.activitySummaryType() ] // Запрос авторизации для этих типов количества. healthStore.requestAuthorization(toShare:typesToShare, read:typesToRead) {[self] (успех, ошибка) в печать (успех) print(ошибка ?? «Нет ошибки») печать (self.dataTypesToRead()) var dataType: HKSampleType? dataType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)! print("readHealthKitData: ", self.readHealthKitData(тип: dataType!)) // Последовательная очередь для обработки образцов и вычислений. очередь.maxConcurrentOperationCount = 1 очередь.имя = "ОчередьДвиженияМенеджера" startWorkout (тип тренировки: .running) } } частная функция queryForUpdates (тип: HKObjectType) { тип переключателя { case HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!: debugPrint("HKQuantityTypeIdentifierHeartRate") по умолчанию: debugPrint("Необработанный HKObjectType: \(type)") } } частная функция dataTypesToRead() -> Set { вернуть Set(arrayLiteral: HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!, HKObjectType.workoutType() ) } /// Типы данных, которые это приложение хочет записать в HealthKit. /// /// — возвращает: набор HKSampleType. частная функция dataTypesToWrite() -> Set { вернуть Set(arrayLiteral: HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate)!, HKObjectType.workoutType() ) } func readHealthKitData (тип: HKSampleType) { let query = HKSampleQuery (sampleType: type, predicate: nil, limit: 1, sortDescriptors: nil) { (запрос, результаты, ошибка) в если пусть ошибка = ошибка { // Обработка ошибки запроса print("Ошибка запроса частоты пульса: \(error.localizedDescription)") возвращаться } если let heartRateSample = результаты?.сначала как? HKQuantitySample { // Доступ к значению сердечного ритма let heartRate = heartRateSample.quantity.doubleValue(for: HKUnit(from: "count/min")) // счетчик = HeartRate self.counter = Int(heartRate) self.sendDataMessage(для: .sendHRToFlutter, data: ["counter": self.counter]) print("Самосчетчик сердечного ритма: \(self.counter) BPM") // Вы можете обновить пользовательский интерфейс или выполнить другие действия с данными о частоте пульса print("Запрос сердечного ритма: \(heartRate)") } } healthStore.execute(запрос) } } расширение WatchViewModel: WCSessionDelegate { сеанс func (_ сеанс: WCSession, активацияDidCompleteWith активацияState: WCSessionActivationState, ошибка: Ошибка?) { } // Получаем сообщение от AppDelegate.swift, отправляемое с устройств iOS сеанс func (_ сеанс: WCSession, сообщение DidReceiveMessage: [String: Any],replyHandler: @escaping ([String: Any]) -> Void) { DispatchQueue.main.async { охранник пусть метод = сообщение ["метод"] как? Строка, пусть enumMethod = WatchReceiveMethod(rawValue: метод) else { возвращаться } переключить enumMethod { случай .sendHRToNative: self.counter = (сообщение["данные"] как? Int) ?? 0 } } } func sendMessage(для метода: String, data: [String: Any] = [:]) { Guard session.isReachable еще { возвращаться } let messageData: [String: Any] = ["метод": метод, "данные": данные] session.sendMessage(messageData, AnswerHandler: ноль, errorHandler: ноль) } } [*]Я пытался добавить watchconnectivity.framework в приложение-компаньон. [*]Условный импорт для WatchKit, но решение не получено, вместо этого отображается, что используемый компонент недоступен в iOS.
-
- Похожие темы
- Ответы
- Просмотры
- Последнее сообщение
-
-
Приложения WatchKit должны иметь цель развертывания, равную iOS 8.2 (было 8,3)?
Anonymous » » в форуме IOS - 0 Ответы
- 10 Просмотры
-
Последнее сообщение Anonymous
-