Post

Replies

Boosts

Views

Activity

Reply to Button Responsiveness Problems in SwiftUI Apps After Upgrading to iOS 18
Indeed the issue appears when the parent view has onTapGesture or onLongPressGesture, and then buttons and sliders stop working. One approach is to use simultaneousGesture, where the issue is that a child view cannot prevent it from running. In every sane framework, events propagate from child to parent, allowing the child to stop propagation. Apple being Apple think different and made it the other way around. Example: if I want to use onTapGesture to hide the keyboard when a user taps outside a TextField, that's impossible. Why this behaviour is not the default is beyond me. The best approach for iOS 18 is simultaneousGesture with LongPressGesture, but that doesn't work on iOS 15 where a onLongPressGesture is required with a onTapGesture before it, just to keep the UI from breaking. Of course that does not work on iOS 18. On macOS a simple onTapGesture does the trick. A view modifier with conditional compilation does the trick. Through some voodoo magic I found a way to discover if the current responder is a TextField and then I can hide it. But I can't find a way to detect if a tap is within a TextField or not.
1w
Reply to Do I still need "iOS 17.0" support, now that my device is running 17.1.2?
Short story: it will work after reboot. It's funny I hit the same issue last week, but for a different reason. I noticed in Disk Utility, that a couple of iOS 17 disk images are mounted. So I decided to tidy-up and unmount them. The following day Xcode had no iOS support. So I installed that and it still didn't work. Restarting Xcode did not help. A reboot solved it. I would say this is a bug and poor design in Xcode. If it needs these images to be mounted, then it should mount them and not ask to download again. The T2 chip on Intel Macs runs Bridge OS. I'm not sure which components are required, but if you clean more than what you need, you should be able to let Xcode download what it needs and reboot. Then try to copy them from here /Library/Developer/CoreSimulator/Images/. I'm not sure if it will work. In your case the Internet speed is a factor, so do cleanup and reinstalls it overnight. It took about 10 minutes and 8 GB of my unlimited LTE data. hdiutil info |grep image-path image-path : /Library/Developer/CoreSimulator/Images/C1DB187D-7E6B-44D7-9ECD-A1A2BE8257B3.dmg Good luck!
Dec ’23
Reply to Double value cannot be converted to UInt8 because the result would be less than UInt8.min
I am surprised that this isn't what happens in the compiled code anyway. I.e. given your source code I would expect the compiler to generate code that keeps v in a register through all of that and not read and write to the global variable location Anyway, I suggest that you look at the generated code (if you can work out how to do that) and see if it is really doing what you think it is doing. View disassembly would be my first thing on a development board 👍🏻 Yes, in C/C++ it is very easy to control if the data will be kept in a register or loaded again. I wonder if Swift can be told to do so? Considering value is actually cnc.data.controller_gyro.value, a plain variable three ObservableObject classes deep (code below). And there are a couple of quick if checks, so the compiler may choose to load value again. Maybe you have compiled with no optimisation? Maybe your real code is more complicated than what you've posted? By default the iPhone run target is Debug, which should disable optimisations. I tried Release as well, and looked into the disassembly in both cases, but it is very hard for me to follow what ASM correspond to what Swift line. I usually have them side by side when debugging embedded boards, can I do this with Xcode? I get the impression value is loaded again. I write and debug Cortex-M ASM in kernel mode, but Swift seems to generate a lot of code for simple operations. If you wish I can post a screenshot? That takes a queue on which it will invoke the callback. If you pass a serial queue there, you should not get concurrent calls. In that case I should change my code to this let queue = OperationQueue() queue.name = "gyroscope" queue.maxConcurrentOperationCount = 1 queue.qualityOfService = .userInteractive motion.startGyroUpdates(to: queue, withHandler: gyro_handler) How are you actually getting this gyro data? Original implementation class controller_gyro_t: ObservableObject { @Published var active: Bool = false let start: TimeInterval = 0.20 let delay: TimeInterval = 0.045 let fast: TimeInterval = 0.00 var last: TimeInterval = 0 var timestamp: TimeInterval = 0 var value: Double = 0 var angle: UInt8 = 0 var ack: Bool = false func stop() { if active { active = false } angle = 0 value = 0 } } struct ContentView: View { // ... #if !os(macOS) let motion = CMMotionManager() #endif func gyro_handler(data: CMGyroData?, err: Error?) { if cnc.data.stop || cnc.data.return_home { DispatchQueue.main.async { stop_controller() } return } if !cnc.data.controller_gyro.active { DispatchQueue.main.async { stop_controller_gyro() } return } if let gyro = data { let now = gyro.timestamp let gyro_dt = now - cnc.data.controller_gyro.last cnc.data.controller_gyro.last = now if gyro_dt > cnc.data.controller_gyro.start { // the last packet was very long ago // usually the first sample when we start receiving updates return } // rotation rate: rad/s var value = cnc.data.controller_gyro.value - gyro.rotationRate.z * gyro_dt * angle_rad_to_deg if value < angle_min { value = angle_min } else if value > angle_max { value = angle_max } cnc.data.controller_gyro.value = value if cnc.data.controller_gyro.ack { cnc.data.controller_gyro.ack = false cnc.data.controller_gyro.timestamp -= cnc.data.controller_gyro.fast } if cnc.data.controller_gyro.timestamp > now { return } let angle = UInt8(value.rounded()) if cnc.data.controller_gyro.angle != angle { cnc.data.controller_gyro.angle = angle cnc.data.controller_gyro.timestamp = now.advanced(by: cnc.data.controller_gyro.delay) let a = "a\(angle)" send_no_log(a) DispatchQueue.main.async { cnc.data.angle = Float(angle) } } } } func btn_gyro_control() -> some View { Button { haptic_feedback() if cnc.data.controller_gyro.active { stop_controller() send_cmd(cmd_stop) } else { stop_controller() cnc.data.stop = false cnc.data.return_home = false cnc.data.controller_gyro.active = true cnc.data.controller_gyro.value = angle_mid cnc.data.controller_gyro.angle = UInt8(angle_min) send_cmd("u") #if !os(macOS) if motion.isGyroAvailable { DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + cnc.data.controller_gyro.start) { motion.startGyroUpdates(to: OperationQueue(), withHandler: gyro_handler) } } #endif } } label: { Image(systemName: cnc.data.controller_gyro.active ? "gyroscope" : "gyroscope") .button_style_image( cnc.connected ? cnc.data.controller_gyro.active ? colour_store : colour_btn : colour_disabled, fg: cnc.connected ? .white : colour_btn_fg_disabled ) } .font(.system(size: size_font_img, design: Font.Design.rounded)) .keyboardShortcut("u", modifiers: [.command]) } // ... } PS: Thank you for your brainstorming ideas 😊 And sorry to the late replay. I had some issues porting the Mac Catalyst version of the app to Mac. Broken Slider and keyboardShortcut to name a few. Hint: the following code works well on iPhone and Mac Catalyst. Try Mac. Hint: play with step or without. @State var inclination: Double = 0 //... Slider(value: $inclination, in: -80...80, step: 0.01) Button("press option+1"){ print("option+1") }.keyboardShortcut("1", modifiers: [.option]) Button("¡surprise!"){ print("¡surprise!") }.keyboardShortcut("¡", modifiers: [.option])
Dec ’23
Reply to Double value cannot be converted to UInt8 because the result would be less than UInt8.min
The traditional fix would be a mutex that protects value. That's not necessarily sufficient for correctness. Indeed for correctness it is not. In C I would do atomic sub and fetch. There's also the question if it is worth switching to kernel space to lock a mutex on code that runs frequently and would not cause any noticeable side effects. In this case I would say no. I guess that the "modern Swift / Apple way" to do this would involve a serial queue. I just learned how to use Swift queues yesterday. 😊 Coming from C/C++ it took a day to bend my mind and get things running smoothly. Namely replacing a cached Path object in use by SwiftUI from a worker thread is a good way to trigger double free. Memory management in Swift might use some improvement. They relay on the assumption that you do everything on the main thread. At my opinion, queues are an easy way to serialise activities or run a group of things concurrently. I wonder how efficient they are? First there is code to manage the queue, and then if an operation has to wait, a context switch is required to process another. Or does the whole queue stall? I've seen projects in Zephyr OS, where they create objects to pass data between tasks. It's handled in serialised work queues. And it's horribly slow, complex and prone to errors. When I designed nano RTOS, I figured I can make IO calls somewhere in between synchronous and asynchronous. In theory the calls might block, in practice they don't. The user can write simple linear code: receive, process, send all in one task. It does not drop data. The OS takes care of the rest.
Dec ’23
Reply to Double value cannot be converted to UInt8 because the result would be less than UInt8.min
Thanks, that's why there is a check: if value is negative, I set it to zero. The only case in which this could crash is if two instances of my function are ran concurrently, which does happen during heavy load, CMMotionManager seems to call my handler again before the previous instance returns. Even though there is little code after the check, the second instance could set value to negative, which would produce the crash. Then it was able to change it back to zero right before the debugger stopped the application. That last part got me confused. The solution was to use a local variable, that is checked and corrected. Moral of the story: always work with local copies of anything that should not be changed externally. Cheers!
Dec ’23
Reply to Xcode 14 failed to prepare iOS 15.7 device?
Fix: cd /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport sudo ln -s 15.5 15.7 Explanation: Xcode 13.4.1 contains DeviceSupport folders up to 15.5 and works with iOS 15.7 devices. 15.7 is missing in 14.0, it will try to use 15.6, which fails to prepare the iOS device for development. If we copy 15.5 to 15.7 or create a link, the 15.5 folder is used, which works for iOS 15.7. My iPhone 7 Plus is recognised instantly over the network, and Developer menu appears in Settings.
Sep ’22