Assumptions
I'm using SFSafari with Swift, but I'm having issues with typing on a third-party keyboard, so I'd like to use a standard (hardened) keyboard.
Addendum 1: After clearing the cache of Safari, it is fixed only at the next startup, but it can not be done to clear the cache. .. ..
Addendum 2: Opening another application with the keyboard that does not move open, and returning again, it was possible to input. What's going on inside ... Can this event happen intentionally?
The problem you are having
What I want to do is the same as 【Swift】 カスタムキーボードを無効化する,
It seems that App Delegate cannot be used on iOS 13 or later, so I wrote it like Scene Delegate, but no error occurs, but even if you execute it, a frozen keyboard of a third party appears.
Applicable source code
Code on the site
func application (_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplicationExtensionPointIdentifier)-> Bool (
if extensionPointIdentifier == .keyboard {
return false
}
return true
}
Code rewritten in Scenne Delegate
func scene (_ scene: UIScene, willConnectTo session: UISceneSession, options a: UIApplication.ExtensionPointIdentifier)-> Bool (
if a == .keyboard {
return false
}
return true
}
What I tried
AppDelegate is executed by looking at here.
(Deleted the Scene Delegate) => It stopped working normally due to blackout.
There were several different ways to write the contents of scene () without error, so I tried it.
Additional information (FW / tool version, etc.)
XCode11.4.1
Basically I was able to do this. But I didn't know how to mess with the keyboard with SFSafariViewController, so I used WKWebView. (I used SFSafari because I had a bug and couldn't use it before.)
you should change the line breaks a little.
Related
I have an application with a deployment target on iOS 14 and built entirely with swiftui.
I have added an intentdefinition file and added a couple of intents with their respective handlers, and I have added to my app delegate the function to handle them:
struct ...App: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
...
}
extension AppDelegate {
func application(_ application: UIApplication, handlerFor intent: INIntent) -> Any? {
switch intent {
case is NewTaskIntent:
return NewTaskHandler()
case is TodayTasksIntent:
return TodayTasksHandler()
default:
return nil
}
}
}
The problem is that this function is never called and when I run the shortcut from its app it always launches my app.
The info Plist contains the keys correctly:
<key>INIntentsSupported</key>
<array>
<string>NewTaskIntent</string>
<string>TodayTasksIntent</string>
</array>
Any idea what is happening?
If you are using SwiftUI, try onContinueUserActivity(_:perform:).
https://developer.apple.com/documentation/swiftui/view/oncontinueuseractivity(_:perform:)
Note: its first argument activityType is defined in your main app's Custom iOS Target Properties with NSUserActivityTypes key. This property seems to be generated by Xcode auto-ly.
Inspired by the "Step Six" in https://toolboxpro.app/blog/adding-shortcuts-to-an-app-part-four
However, it's quite wired that my func application(_ application: UIApplication, handlerFor intent: INIntent) -> Any? triggered yesterday, but makes no sense right now. Is it a 'feature' 😂
I am trying to test my app clip's url handler by running the app clip from Xcode. However the URL method handler (SceneDelegate's continue method) never gets called, contrary to Apple's documentation documentation, which states:
For a UIKit-based app clip and full app that support scene-based app
life-cycle events, implement the callbacks defined in UISceneDelegate.
For example, implement scene(_:continue:) callback to access the user
activity object.
For a UIKit-based app clip and full app that respond
to app-based life-cycle events, implement the callbacks defined in
UIApplicationDelegate. Be sure to implement the
application(:continue:restorationHandler:) callback, because you
don’t have access to the NSUserActivity object in
application(:didFinishLaunchingWithOptions:).
The app delegate does not implement the application(_:continue:restorationHandler:) method
The app clip's scheme has the _XCApplClipURL parameter enabled and set to https://fruits.com/check?fruit_name=bananas
The app clip's Associated Domain lists appclips:fruits.com
The app clip's SceneDelegate is as follows
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
// UGHH!!! Never gets called
print("AppClip invocation url is : \(incomingURL)")
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// OK!! This gets called
guard let _ = (scene as? UIWindowScene) else { return }
}
}
I have been banging my head on the walls for the last 2 days. What am I missing?
Note: I am using this sample app available in github, just modified the signing configuration to get the app clip to compile & run.
The continue method mentioned is only called when your app is invoked after it was previously opened. In order to get the value set in _XCApplClipURL the first time your app is launched, you need to use the second method you mentioned (scene willConnectTo session).
You can try something like this:
if let activity = connectionOptions.userActivities.filter({ $0.activityType == NSUserActivityTypeBrowsingWeb }).first {
if let url = activity.webpageURL {
print("incoming URL: \(url)")
}
}
Thanks for your using AppsFlyer's public demo of App Clip and for highlighting this issue. I really appreciate it.
I can confirm you were doing great, this functionality was broken sometime after Beta 5.
Please follow and comment on this issue here
I have a watchOS 4 app which displays SpriteKit animations (SKActions) on top of the UI. Everything works fine in simulator and also on device first couple of times, then after some time when app is in background, and it is started, animations just freeze and completion block for the most long-lasting animation is not called. Any idea what might be the issue?
This is how I run my actions, caller is waiting for completion closure in order to hide the spritekit scene:
private func runActions(with icon: SKShapeNode?, completion: #escaping () -> Void) {
if let icon = icon, let scaleAction = scaleAction, let bg = background {
self.label?.run(fadeInOutAction)
icon.run(scaleAction)
icon.run(fadeInOutAction)
bg.run(backgroundAction, completion: completion)
} else {
completion()
}
}
And yes, I am aware that SKScene is paused when app moves to background. I am doing this in willActivate of my InterfaceController:
if scene.scene?.isPaused == true {
scene.scene?.isPaused = false
}
I want to emphasize that this works first always. It begins to fail after the app has been backgrounded for some time. Especially if I start the app from complication and try to immediately fire these animations, then this freezing happens.
Can I answer my own question? I guess I can? Here goes:
I finally solved this. It turns out that the WKInterfaceScene in WatchKit has ALSO an isPaused property that you need to turn false sometimes. So now in willActivate of my InterfaceController I will also check that and turn it false if it is true. Since I made this change, I haven't seen a single hiccup, freeze or anything weird anymore.
Case closed, I guess. I leave this here for future generations who might face this issue.
Since Xcode 7 we have a nice API for UI testing.
Mostly I'm satisfied with it. The only concern is related to the speed.
In the beginning an ordinary UI test case (about 15 actions) ran approximately 25 seconds. Then I mocked networking completely. Now it takes 20 seconds. Considering the fact that the time is taken only by animations and a launch time (1 second or even less), I assume, there must be a way to speed it up.
Try setting this property when your UI tests run:
UIApplication.shared.keyWindow?.layer.speed = 100
Here's how I set it:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if ProcessInfo.processInfo.arguments.contains("UITests") {
UIApplication.shared.keyWindow?.layer.speed = 100
}
}
And in my UI tests:
class MyAppUITests: XCTestCase {
// MARK: - SetUp / TearDown
override func setUp() {
super.setUp()
let app = XCUIApplication()
app.launchArguments = ["UITests"]
app.launch()
}
}
There's a few more handy tips in this blog post.
Another possibility is to disable animations at all:
[UIView setAnimationsEnabled:NO];
Swift 3:
UIView.setAnimationsEnabled(false)
Following #Mark answer, the Swift 3 version:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if ProcessInfo.processInfo.arguments.contains("UITests") {
UIApplication.shared.keyWindow?.layer.speed = 200
}
}
On you ui test file:
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
let app = XCUIApplication()
app.launchArguments = ["UITests"]
app.launch()
Add it in didFinishLaunch
[UIApplication sharedApplication].keyWindow.layer.speed = 2;
The default value is 1, make it 2 to double its speed.
Run them in parallel.
EDIT: This may be outdated since my original answer in 2019, since Xcode allows now testing on multiple simulators within one machine:
If you have only 1 build machine, you can use Bluepill: https://github.com/linkedin/bluepill
EDIT: However, I don't use either of these (bluepill / Xcode), so I'll keep mention of the bluepill in this answer, maybe it has some uses.
If you have multiple machines, you can use Emcee: https://github.com/avito-tech/Emcee (it also works for a single machine setup)
We have 50 hours of UI tests, and Emcee allows us to run them in 1 hour. There are several tricks to make UI tests faster, but it is pointless if you are not running them in parallel. E.g. you can't make your 25 seconds tests run in 0.5 second.
Tricks:
Run XCUIApplication without reinstalling it:
XCUIApplication(
privateWithPath: nil,
bundleID: "your.bundle.id"
)
Note: you should launch app with XCUIApplication().launch() at least once per launching XCUI test runner to install the app. This requires usage of private API.
You can move something to background thread. E.g.
let someDataFromApi = getSomeData() // start request asynchronously and immediately return
launchApp() // this can be few seconds
useInTest(someDataFromApi) // access to data will wait request to finish
You can make the code look like there is no asynchronous things, it would be easier for QA. We want to implement this, but we didn't, so it is just an idea.
Switch to EarlGrey and make tests that lasts few seconds (but they will not be black box).
Open screens via deep links if you have deep links.
Use a lot of private API and other hacks. E.g.: instead of going via UI to Settings app and then reset privacy settings you can call some private API.
Never use long sleeps. Use polling.
Speed up animations. Do not disable them! (we use layer.speed = 100 on every view and we got severe problems with the value of 10000, full code: https://pastebin.com/AnsZmzuQ)
Pass some flags from UI tests to app to skip initial alerts/tutorials/popups. Can save a lot of time. Be sure to have at least 1 test that checks that those alerts work.
Advertisement: most of these is implemented in https://github.com/avito-tech/Mixbox. Disclaimer: I'm the main contributor in this test framework. Its tedious to set up, but at least you can reuse some code if you don't want to reuse whole framework.
I wanted to disable ALL animations during Snapshot testing. I was able to able to achieve this by disabling both Core Animation and UIView animations as below.
Note because my app used storyboards UIApplication.shared.keyWindow was nil at launch so I access the UIWindow by referring to the window property directly.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if ProcessInfo.processInfo.arguments.contains("SnapshotTests") {
// Disable Core Animations
window?.layer.speed = 0
// Disable UIView animations
UIView.setAnimationsEnabled(false)
}
return true
}
I decrease my UITests time in 30%, follow all steps:
When you run your app, add the argument:
let app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = false
app.launchArguments += ["--Reset"]
app.launch()
}
Now, in your AppDelegate add:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
setStateForUITesting()
}
static var isUITestingEnabled: Bool {
get {
return ProcessInfo.processInfo.arguments.contains("--Reset")
}
}
private func setStateForUITesting() {
if AppDelegate.isUITestingEnabled {
// If you need reset your app to clear state
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
// To speed up your tests
UIApplication.shared.keyWindow?.layer.speed = 2
UIView.setAnimationsEnabled(false)
}
}
In your code, to verify if is in test mode, you can use:
if AppDelegate.isUITestingEnabled {
print("Test Mode")
}
Additionally, to can wait while the element load I created this extension:
import XCTest
extension XCUIElement {
func tap(wait: Int, test: XCTestCase) {
if !isHittable {
test.expectation(for: NSPredicate(format: "hittable == true"), evaluatedWith: self, handler: nil);
test.waitForExpectations(timeout: TimeInterval(wait), handler: nil)
}
tap()
}
}
Use like this:
app.buttons["start"].tap(wait: 20, test: self)
Note that keyWindow is deprecated as of iOS 13. Specifically,
UIApplication.sharedApplication.keyWindow and self.window.keyWindow within the AppDelegate both return nil. This answer says to loop through UIApplication.shared.windows and find the one with isKeyWindow true, but in my test using ios14, even that return false for my only window. In the end, setting self.window.layer.speed worked for me.
I am trying to understand doing Quick Actions (3D Touch) for iOS 9.
I wanted the user to select 1 of 4 filter to be applied to image, so if I select item 1, I will set the NSUserDefaults.standardUserDefaults() to the filter, then show the correct picture with the applied filter.
In AppDelete.swift:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
var filterType:Int
switch (shortcutItem.type) {
...set filterType
}
NSUserDefaults.standardUserDefaults().setInteger(filterType, forKey:"filterType")
NSUserDefaults.standardUserDefaults().synchronize()
}
In ViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector:"setDefaultFilter", name: UIApplicationWillEnterForegroundNotification, object:nil) // Handle enter from background
setDefaultFilter()
}
func setDefaultFilter() {
filterType = defaults.integerForKey("filterType")
...
imageView.image = filterImage(defaultImage!, filter:filters[filterType])
}
However, when enter the app from the menu, it will always show the last selection (not the current selection). If I select item 1, nothing happened. I select item 3, item 1 will appeared.
I have also try passing parameters via appDelegate and the result is the same. I believe there are some issues with life cycle.
Any ideas?
NSUserDefaults write data on flash, which may not be so fast.
You can wait a little longer, like observe UIApplicationDidBecomeActiveNotification other than UIApplicationWillEnterForegroundNotification.
Or you can use other ways to pass params, e.g., as an instance variable in AppDelegate.
didFinishLaunchingWithOptions method is always called before calling performActionForShortcutItem method to response to the quick action.
So, I think that you need to check what kind of quick action is selected in didFinishLaunchingWithOptions method. If the app is not launched from quick action, you just continue to your normal app launching process.(default filter)
And if you decide to handle quick action in didFinishLaunchingWithOptions, you have to return NO in didFinishLaunchingWithOptions.
You could get more idea from my demo project:
https://github.com/dakeshi/3D_Touch_HomeQuickAction