Group Group Group Group Group Group Group Group Group

SwiftUI on macOS

I’ve built an app as Multiplatform. It compiles on all platforms without error or warning.
All the code is in the shared files, I’ve added nothing specific for iOS or macOS.

It runs correctly on iPhone and iPad. It also seems to run on macOS, producing the interface I’m expecting but it is not working under the hood. No crashes, just incorrect behaviour. All that’s expected to happen is that I enter data into TextFields and it is validated and added to UserDefaults. In the iOS version, invalid data is rejected and valid data stored. In the macOS version (identical code) all input is rejected.

Any thoughts?

Here’s what I found out:

On iOS, when a field gets focus it emits onEditingChanged(true) and when it loses focus without a commit (i.e.) it emits onEditingChanged(false); when it loses it with a commit it emits onCommit() and then it emits onEditingChanged(false). IMHO, these last two are not in the correct logical order. I think you stop editing and only then do you commit.

On macOS, when a field gets focus it remains silent but once you type anything, it emits onEditingChanged(true); when it loses focus with or without a commit it emits onEditingChanged(false) followed by onCommit(). IMHO, these arrive in the correct logical order but we cannot distinguish between committing and abandoning the editing.

These two very different ways of handling TextFields mean that we’re only kidding ourselves if we pretend we can use the same code on iOS and macOS.

Can anybody suggest how to make the two compatible?

I can confirm your findings which suggest to me that despite being SwiftUI, these are basically AppKit & UIKit under the hood.

My suggestion would be not to reply on losing focus to trigger the change but to have a Save button that triggered the validation.

You can still use onCommit to detect the Return key and trigger your validation function, but a combination of onCommit and a Button seems to me the most robust solution.

I think the user shouldn’t have to cope with a (separate) button for each field, clogging up his interface. I got really nice button-free behaviour on iOS when I built a single generic View which can handle any data type (not just strings like TextField), with custom validation:

  CustomField<T: CustomDatumProtocol>(
       caption: String,
       subcaption: String,
       value: T,
       fieldValidator: FieldValidator(validator: (T) -> Bool, errorMessage: String))

It gives the user commit/revert on valid/invalid and revert-only if (s)he abandons the edit by changing focus. Unfortunately, I have been unable to implement abandon on my mac because I cannot distinguish between the two cases!!

This is super annoying for you.

My general advice is to avoid the multi-platform app and create two separate apps in a workspace so you can share some code but then have different files where necessary. This removes all the messy compiler directives. I think it will also make both apps more stable.

I realize that you have already put a lot of work into a multi-platform app, so this is not what you want to hear, but that is definitely my opinion.

That’s exactly what I did. Nearly all the code is shared, with small code differences in platform specific directories (same filename, different code).

That does nor deal with the fact that on macOS, I cannot tell if the user has committed or abandoned the field as the same sequence of closures is called in either case.