TLDR: How can I listen to changes in SwiftData and share the data via WatchConnectivity?
I am developing an app for the Apple Watch where the iPhone can act as a remote for the Watch.
The App on the Watch should work independently from iPhone. The user can create elements on the Watch and start the action associated with the element. The iPhone user can see the elements created on the Watch and start the action on the Watch from the iPhone (if possible I want to add the functionality to let the user update the elements on both devices, but that's not a priority atm).
I tried to get it to work with CloudKit and SwiftData, but somehow I couldn't get CloudKit to initialize correctly. After further research, I reconned that using WatchConnectivity and ApplicationContext should be a suitable approach.
To communicate with the iPhone App, I created a singleton class that manages the communication. Now I want this class to listen to the changes in SwiftData to send the elements to the iPhone, but I don't know how I can get access to the ModelContext in the Communicator class. I read that passing the value from the initializer of a view is not good practice and it didn't work for me.
What would be a suitable way to achieve the desired behavior of my app? Any help and feedback is appreciated. TIA
Post
Replies
Boosts
Views
Activity
I want to add shortcut and Siri support using the new AppIntents framework. Running my intent using shortcuts or from spotlight works fine, as the touch based UI for the disambiguation is shown. However, when I ask Siri to perform this action, she gets into a loop of asking me the question to set the parameter.
My AppIntent is implemented as following:
struct StartSessionIntent: AppIntent {
static var title: LocalizedStringResource = "start_recording"
@Parameter(title: "activity", requestValueDialog: IntentDialog("which_activity"))
var activity: ActivityEntity
@MainActor
func perform() async throws -> some IntentResult & ProvidesDialog {
let activityToSelect: ActivityEntity = self.activity
guard let selectedActivity = Activity[activityToSelect.name] else {
return .result(dialog: "activity_not_found")
}
...
return .result(dialog: "recording_started \(selectedActivity.name.localized())")
}
}
The ActivityEntity is implemented like this:
struct ActivityEntity: AppEntity {
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "activity")
typealias DefaultQuery = MobilityActivityQuery
static var defaultQuery: MobilityActivityQuery = MobilityActivityQuery()
var id: String
var name: String
var icon: String
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(self.name.localized())", image: .init(systemName: self.icon))
}
}
struct MobilityActivityQuery: EntityQuery {
func entities(for identifiers: [String]) async throws -> [ActivityEntity] {
Activity.all()?.compactMap({ activity in
identifiers.contains(where: { $0 == activity.name }) ? ActivityEntity(id: activity.name, name: activity.name, icon: activity.icon) : nil
}) ?? []
}
func suggestedEntities() async throws -> [ActivityEntity] {
Activity.all()?.compactMap({ activity in
ActivityEntity(id: activity.name, name: activity.name, icon: activity.icon)
}) ?? []
}
}
Has anyone an idea what might be causing this and how I can fix this behavior? Thanks in advance
I am trying to include AppIntents into my app. To achieve this, I am following Apple‘s WWDC Session. Everything works great until I try to add a parameter to my intent.
My Entity and Query look like this:
struct ActivityEntity: AppEntity {
var id: String
var name: String
static var typeDisplayRepresentation: TypeDisplayRepresentation = .init(name: "Activity")
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: LocalizedStringResource(stringLiteral: self.name))
}
static var defaultQuery: MobilityActivityQuery = MobilityActivityQuery()
}
struct MobilityActivityQuery: EntityQuery {
func entities(for identifiers: [String]) async throws -> [ActivityEntity] {
Activity.all()?.compactMap({ activity in
identifiers.contains(where: { $0 == activity.name }) ? ActivityEntity(id: activity.name, name: activity.name) : nil
}) ?? []
}
}
My Intent looks like this:
struct StartSessionIntent: AppIntent {
static var title: LocalizedStringResource = "Start Recording"
@Parameter(title: "Activity")
var activity: ActivityEntity?
func perform() async throws -> some IntentResult & ProvidesDialog {
var activityToSelect: ActivityEntity? = self.activity
if activityToSelect == nil {
activityToSelect = try await self.$activity.requestDisambiguation(among: Activity.all()?.map { ActivityEntity(id: $0.name, name: $0.name) } ?? [])
}
guard let selectedActivity = Activity[activityToSelect?.name ?? ""] else {
return .result(dialog: "Failed to get activity")
}
Session.shared.setActivity(activity: selectedActivity)
Session.shared.startSession()
return .result(dialog: "Okay, starting to record this mobility")
}
}
As soon as the perform method accesses activity, the execution fails and I get the notification from Shortcuts saying: The action contains invalid metadata.
In the console the only message I get is this: [Execution] Unknown NSError Communication with the Helping-App is not possible. (this might be a bad translation from me)
I am completely clueless on how I can debug this problem and my internet search didn‘t help either. Any help is much appreciated. Thanks in advance
I want to send multiple messages to a web socket like this:
let session = URLSession(configuration: .default)
var socket = session.webSocketTask(with: URLRequest(url: self.url, timeoutInterval: 30)
socket.resume()
do {
for message in messages {
try await socket.send(message)
}
} catch {
print(error)
}
Sometimes all messages are sent, but most times the 10th messages simply does not finish nor fail. I even added delegate methods to detect if the socket is closed or if something failed, but those delegate methods never get called. The send simply does not return nor throw.
I thought that adding the timeoutInterval would cause the send to fail if it didn’t succeed after 30 seconds, but that does not seem to be the case.
Has anyone an idea what could be happening here and how I can resolve this? Any help is greatly appreciated, thanks in advance.
PS: the message it failed on mostly is only about 79 Bytes, so it should not take that long to send, especially since sending far longer messages works just fine
EDIT: I just tried to use the synchronous version of send with the completionHandler as I hoped that maybe the async version might be bugged, but I have the same problem using that, no completion handler is called
I am currently working on monitoring the motions of a person using headphones a the CMHeadphoneMotionManager. Since I want to process the motion, I need the updates to be as frequently as possible to improve the quality of the data. In addition to processing the data, I want to display it live in a chart and save the data to CoreData in order to access them later and try other methods for processing on previously recorded data.
I created a UML diagram of my current approach:
The MotionManagerProtocol is the protocol of the class MotionManager, which handles the CMHeadphoneMotionManager. MotionManager.start looks like this:
func start() {
self.manager.startDeviceMotionUpdates(to: OperationQueue.main) { motion, error in
if let motion {
let timestamp = CACurrentMediaTime()
self.timeInterval = self.oldTimestamp < 0 ? 0 : timestamp - self.oldTimestamp
self.oldTimestamp = timestamp
self.userAcceleration = motion.userAcceleration
self.rotationRate = motion.rotationRate
self.attitude = motion.attitude
}
if let error {
print(error)
}
}
}
The attributes like userAcceleration etc are just setting the value of the CurrentValueSubject so that other classes can subscribe to changes in them.
The MotionRecorder, MotionViewModel and SpeedCalculator are simply subscribing to changes in the CurrentValueSubject of the MotionManager to be notified when new data is available.
In order to be sure, that all are using the same MotionManager, I created a Singleton for it.
Is this structure of my code a decent approach or are there anything I can change to make it scale better and receive the new motion data as fast as possible? Since I am pretty early in the process, I can don’t really loose anything if I have to change the whole architecture of my code, so feel free to give advice in this direction as well.
Any advice and tips on what to learn in order to improve this project are very much appreciated and TIA.
PS: I am using SwiftUI as my framework of choice
I want to move entries in a List between Sections. The .onMove() modifier does not work between sections, so I figured I could use normal drag and drop.
I found a good example on how to do this between two Lists, but since I don’t want to drag between two Lists, but between the Sections, the Example does not work.
I tried to modify the example to not have two separate Lists in a Stack. Now when I try to insert my element into another section, .onInsert() is always called on the section where I dragged it from, no matter where I drop it. Is this simply a limitation of SwiftUI or am I missing something?
Any advice on better solutions for this is also very much appreciated. Thanks in advance
I am calling Swifter.authorize(withCallback: URL, presentingFrom: UIViewController, success: Swifter.TokenSuccessHandler, failure: Swifter.TokenFailureHandler)
from a buttonpress in a UIViewControllerRepresentable
. When clicking the Button, I am directed to a website in Safari to authorize my App. So far so good. After authorizing my app and getting returned by the Callback URL: “MyTwitterApp://“ neither the code inside Swifter.TokenSuccessHandler
nor inside Swifter.TokenFailureHandler
is run. Even the debugger does not step into it. In addition, at buildtime I get many warnings about init(account:)
being deprecated since iOS 11.0 along other stuff. As I am using the newest version of Swifter and as I have found a fairly recent tutorial (definitely created after the launch of iOS 11) showing Swifter working, I am not sure if that creates any problem.
However, my main complaint is, that I am not able to authorize my app, as the code in success is not accessed.
I want my app to decide whether sleep mode should be enabled by checking how many bluetooth peripherals are connected. For bluetooth I created a Singleton, which contains an array of all the connected peripherals. To disable sleep mode I found the following line of code online:UIApplication.shared.isIdleTimerDisabled = trueSince the state of the sleep mode should always be updated after the number of connected peripherals changed, I could not figure out where to put that line of code.Sorry if that's a novice question, but I am pretty new to programming, so I would appreciate any help. Maybe there is a better way to achieve the wanted behavior and I would be happy to learn about it. Thanks in advance.
I am trying to create a progress bar which updates based on two values received via bluetooth (should represent a race like thing, so based on which value is higher, the progress bar grows or shrinks). For updating the view I followed this tutorial: https://dippnerd.com/swiftui-timer-ui-refresh, but for me it is not working. The timer works just fine, but my view does not update. In my root view I do not use the TimerWrapper, I only use it in a secondary view, so I do not have the @EnvironmentObject of TimerWrapper in my root view controller.TimerWrapper:import SwiftUI
import Combine
class TimerWrapper : ObservableObject {
let willChange = PassthroughSubject<TimerWrapper, Never>()
var timer : Timer!
func start(withTimeInterval interval: Double) {
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in
print("timer fired")
self.willChange.send(self)
}
}
}The progressBar view looks as following:import SwiftUI
struct MultiplayerBar: View {
@EnvironmentObject var timerWrapper: TimerWrapper
var body: some View {
GeometryReader { geometryReader in
Rectangle()
.fill(Color.green)
Rectangle()
.fill(Color.red)
.frame(width: getPlayer1Progress(maxWidth: geometryReader.size.width))
.animation(.easeIn)
}
.frame(height: 200)
.cornerRadius(15)
.padding()
}
}
struct MultiplayerBar_Previews: PreviewProvider {
static var previews: some View {
MultiplayerBar()
.environmentObject(TimerWrapper())
}
}And it is called like this:MultiplayerBar()
.shadow(radius: 5)
.environmentObject(TimerWrapper())The timer is started in the SceneDelegate.swift file:if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
let timer = TimerWrapper()
timer.start(withTimeInterval: 1)
window.rootViewController = UIHostingController(rootView: ShowTrainingView()
.environmentObject(BLEControl.BLESingleton)
.environmentObject(UserData()))
self.window = window
window.makeKeyAndVisible()
}Actually I am not quite sure if my approach is right and how I could fix it. Thanks for any help in advance.
I want to insert a new outlet for a stack view, but every time I click connedt, this error appears: "Could not insert new outlet connection: Could not find a classs named ViewController in the language Swift". I tried to clear the project and reopened it, but nothing changed. In my other projects it works just fine. I think the only thing I did was updating Xcode in the AppStore. Is it a problem with Xcode or can I do anything to fix this error?