How to query UITextField wrapped in UIViewRepresentable from a Xcode UI test? - swiftui

I'm developing my app with SwiftUI, but some elements are still from the UIKit world, e.g. one UITextField with special behavior, that's why I use a UIViewRepresentable where the makeUIView methods returns a UITextField.
When I try to enter text via UI test, I don't succeed in querying the textfield; even app.textFields doesn't return anything.
Is this because of using UIViewRepresentable, am I missing something?

I had the same situation you described. Make sure to set accessibilityIdentifier in makeUIView. Also bear in mind that if you're setting isSecureTextEntry to true, you should access it using app.secureTextFields

Related

possible to instantiate WKInterfaceController in SwiftUI Watch project?

I have a project in which I have run into a limitation of SwiftUI on the Apple Watch. My proposed solution was to instantiate a WKInterfaceController to perform the needed functionality, and then return to using SwiftUI views. Is this possible? I tried to wrap my controller in a WKInterfaceObjectRepresentable object, but there doesn't seem to way to instantiate the interface controller from a Storyboard, as is possible on iOS according to this similar case:
How to add Storyboard ViewController into SwiftUI Project?
But Xcode tells me, "Cannot find UIStoryboard in scope"
Is there a way to create an InterfaceController from SwiftUI? Or perhaps I need to use a WKHostingController for my whole Watch project, so as to have access to the option of using InterfaceController objects?
Thanks.
You can navigate to an existing interface controller defined in a watchOS storyboard from SwiftUI by using NavigationLink with the destinationName:label: arguments, see here.
destinationName is the identifier used in the storyboard for that interface controller, eg:
NavigationLink(destinationName: "MyInterfaceControllerIdentifier") {
Text("Go")
}

SwiftUI Map() delegate access

I'm attempting to refactor a SwiftUI application.
From: IUViewRepresentable of MapView
To: SwiftUI's native Map() view.
However my application has to be able to react to the user's panning and zooming. In my UIViewRepresentable version, I added MKMapViewDelagate protocol to the Coordinator class, and create mapView(_ mapView:regionDidChangeAnimated)
How can assign a delegate class to the SwiftUI native version to accomplish this?
I've seen some posts use an init() method to adjust the appearance of the map with MKMapView.appearance(). Turns out this has a delegate property, but assigning a delegate here does not result in the mapView:regionDidChangeAnimated method being called...
Since Map() accepts a Region binding, you can use .onChange to react to the region changing.

SwiftUI: where to put app initialization code so it doesn't run in preview?

I'm trying to find where to put app initialization code that should NOT be run in preview mode. I've seen several answers suggesting it's incorrect to split the app behaviour this way (launch vs preview), but I disagree: many apps need to do additional setup (eg connect to database, launch background tasks, call APIs, etc) that isn't appropriate for the preview (where static test data makes most sense).
In preview mode, Xcode actually runs the app and calls AppDelegate.applicationDidFinishLaunching, so any post-launch initialization code there will be triggered.
What is the recommended way to run app setup code so that it doesn't run in preview?
It appears that Xcode sets an environment variable when running the app for SwiftUI previews. The key is "XCODE_RUNNING_FOR_PREVIEWS", which will have a value of "1".
Given this I found putting a guard statement that checks that environment value in my applicationDidFinishLaunching implementation before the initialization I didn't want to occur for previews fixed my preview (my initialization was making them fail entirely).
I also wrapped it in a DEBUG check to ensure it would not ever accidentally break a production build.
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Initialization needed for previews
#if DEBUG
guard ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] != "1" else {
return
}
#endif
// Further initialization not needed for previews
}
The Preview Live, I assume you mean it, creates complete window scene context and injects there view to be previewed, so all application & scene delegate methods are called including instantiating root ContentView, but the root view is not shown, ie. its body is not called.
Thus you can achieve your goal by placing code, initiating all heavy/network/etc. operations in root view's .onAppear callback. And this, actually, will be good for your users as well, because such approach gives fast application start and initial UI presented.
TL;DR:
Workaround this issue by putting your SwiftUI code in a framework and make it the selected scheme in Xcode before viewing your previews.
Details:
Looks like SwiftUI Previews need to build a target that contains the SwiftUI code (makes sense), but that target doesn't have to be an app! So it's unclear why it needs to run the app at all, but clearly it does. Xcode picks which target to build from the currently selected scheme (if you pick a target that doesn't build the required SwiftUI files, you'll get an error in the previews). So here's a workaround:
Add a new framework target
Put your view code in that new target
Select the scheme for that new target
Run your preview
This also has the advantage of only compiling the new target (and its dependencies) for rendering previews, which is likely to be faster that building the whole app.
Any dependencies will of course need to be accessible from that new target.
This works at least in Xcode 12.5.1

Watchkit popToRootController not working

I am using a vertical page direction on my watch app, and I have a button that opens a new interface, which opens the third interface also from a button, this is done by using the modal view, and in the third interface i am calling popToRootController, because i want to go back to the first interface, but this is not working, have anyone same issue?
You need to use dismiss if you present view controllers modally using presentControllerWithName.
You can use popToRootController or popController if you present the view controllers hierarchically using pushControllerWithName.
You can read more on the Apple Interface Navigation guide.
In your example if you want to dismiss twice, you will have to pass a delegate or closure with the context to your second controller. Then you can call the delegate method or closure after dismiss() on the third controller. The implementation of the delegate method or closure will be another dismiss().

Customize UIAlertController in iOS 8 to include standard elements like UITableView

I am used to customize UIAlertViews through the [alert setValue:someView forKey:#"accessoryView"] method. This creates customizable content for UIAlertViews with custom heights. However it only works on iOS7 and down. In iOS8 the UIAlertController have taken over, and I cannot customize it anymore, it will cut the height of the UIAlertView.
Is it impossible because of misuse of the UIAlertController, or how am I supposed to do it?
I am trying to incorporate a UITableView inside a UIAlertController with UIAlertControllerStyleAlert.
Thx.
I ran into the same issue right now. I looked at the private header for UIAlertController (https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/UIKit.framework/UIAlertController.h) and found a promising property: contentViewController
And it turned out to be exactly the same as accessoryView used to be for UIAlertView, the difference being that you need to assign a UIViewController to this property rather than a UIView.
UIViewController *v = [[UIViewController alloc] init];
v.view.backgroundColor = [UIColor redColor];
[alertController setValue:v forKey:#"contentViewController"];
That piece of code will show a red view on the alert view! Happy UIAlertController customizing ;)
PS. It is a private property but using KVC there shouldn't be a problem App Store wise, I think.
Edit:
Some people complained that this isn't very safe. It's not a public API, so yes, Apple could change it in any release, causing this method to fail.
To make sure your entire app doesn't crash if that happens you could wrap the KVC call in a try block. If the property changes your controller won't show the content view, but it also won't crash:
#try {
[alertController setValue:v forKey:#"contentViewController"];
}
#catch(NSException *exception) {
NSLog(#"Failed setting content view controller: %#", exception);
}
Using this method in production can be risky, and I don't recommend it for important alerts.
I suggest not your wasting time trying to cram additional UI into a place where isn't supposed to be. Based on the last few years of improvements, Apple will probably add a custom view in the next iOS. Until then, have a look at a framework designed to handle this exact situation without subverting any best practices: SDCAlertView
It supports alerts that imitate the native alerts on iOS 7,8,9, including handling all of the nasty edge cases around sizing, button types, rotation, etc. It does support arbitrary custom views within the alert.
I use this library in Yahoo YMPromptKit for custom push notification prompts that look exactly like iOS native. Here's another example:
I think you can easily customize the UIView adding the controls needed and present it modally, unless you have any other specific reason to use only UIAlertController.
https://www.cocoacontrols.com/search?q=UIAlertview
You can do it with just a one of line of code using my UIAlertController category and replace existing alerts in application, check it here.