I have an app with several content blockers extension. Their configuration is fine and they act as they are supposed to.
However I need to call reloadContentBlocker(withIdentifier:completionHandler:) from SFContentBlockerManager when I've updated the filter lists for example.
Here is a code example (with NSLog for timestamp purposes below):
func reload(_ callback: #escaping((Bool) -> Void)) {
NSLog("Reload start")
let contentBlockersIdentifiers = ["com.aaa.bbb.ContentBlocker.ExampleA", "com.aaa.bbb.ContentBlocker.ExampleB", "com.aaa.bbb.ContentBlocker.ExampleC", "com.aaa.bbb.ContentBlocker.ExampleD", "com.aaa.bbb.ContentBlocker.ExampleE"]
var failures: [String] = [String]()
let dispatchSemaphore = DispatchSemaphore(value: 0)
let dispatchQueue = DispatchQueue(label: "reload-queue", qos: .userInitiated)
dispatchQueue.async {
NSLog("Reloading content blockers")
for aContentBlockerIdentifier in contentBlockersIdentifiers {
NSLog("Reloading '\(aContentBlockerIdentifier)'")
SFContentBlockerManager.reloadContentBlocker(withIdentifier: aContentBlockerIdentifier) { (error) in
if let error = error?.localizedDescription {
NSLog("Failed to reload '\(aContentBlockerIdentifier)': \(error)")
failures.append(aContentBlockerIdentifier)
} else {
NSLog("Successfully reloaded '\(aContentBlockerIdentifier)'")
}
dispatchSemaphore.signal()
}
dispatchSemaphore.wait()
}
callback(failures.isEmpty)
NSLog("Reload end")
}
}
This is what is prints:
16:41:43.391543+0200 Reload start
16:41:43.392003+0200 Reloading content blockers
16:41:43.392125+0200 Reloading 'com.aaa.bbb.ContentBlocker.ExampleA'
16:41:50.010102+0200 Successfully reloaded 'com.aaa.bbb.ContentBlocker.ExampleA'
16:41:50.010299+0200 Reloading 'com.aaa.bbb.ContentBlocker.ExampleB'
16:41:50.351554+0200 Failed to reload 'com.aaa.bbb.ContentBlocker.ExampleB': The operation couldn’t be completed. (WKErrorDomain error 2.)
16:41:50.351676+0200 Reloading 'com.aaa.bbb.ContentBlocker.ExampleC'
16:41:50.493327+0200 Failed to reload 'com.aaa.bbb.ContentBlocker.ExampleC': The operation couldn’t be completed. (WKErrorDomain error 2.)
16:41:50.493429+0200 Reloading 'com.aaa.bbb.ContentBlocker.ExampleD'
16:41:50.631578+0200 Failed to reload 'com.aaa.bbb.ContentBlocker.ExampleD': The operation couldn’t be completed. (WKErrorDomain error 2.)
16:41:50.631681+0200 Reloading 'com.aaa.bbb.ContentBlocker.ExampleE'
16:41:50.718466+0200 Failed to reload 'com.aaa.bbb.ContentBlocker.ExampleE': The operation couldn’t be completed. (WKErrorDomain error 2.)
16:41:50.718600+0200 Reload end
It apparently tries to do the reload one content blocker after another (as I wanted to do with the DispatchSemaphore). However after the first one succeeded the following are failures.
Now let's go and disable the Content Blockers in Setting App > Safari > Content Blockers and try again:
16:55:05.699392+0200 Reload start
16:55:05.700171+0200 Reloading content blockers
16:55:05.700564+0200 Reloading 'com.aaa.bbb.ContentBlocker.ExampleA'
16:55:05.714444+0200 Successfully reloaded 'com.aaa.bbb.ContentBlocker.ExampleB'
16:55:05.714909+0200 Reloading 'com.aaa.bbb.ContentBlocker.ExampleB'
16:55:05.723056+0200 Successfully reloaded 'com.aaa.bbb.ContentBlocker.ExampleB'
16:55:05.723343+0200 Reloading 'com.aaa.bbb.ContentBlocker.ExampleC'
16:55:05.730565+0200 Successfully reloaded 'com.aaa.bbb.ContentBlocker.ExampleC'
16:55:05.730775+0200 Reloading 'com.aaa.bbb.ContentBlocker.ExampleD'
16:55:05.735733+0200 Successfully reloaded 'com.aaa.bbb.ContentBlocker.ExampleD'
16:55:05.735841+0200 Reloading 'com.aaa.bbb.ContentBlocker.ExampleE'
16:55:05.740758+0200 Successfully reloaded 'com.aaa.bbb.ContentBlocker.ExampleE'
16:55:05.740865+0200 Reload end
Surprise... it works. But I would rather not ask my users to :
go manually disable the Content Blockers in the Settings
perform the update manually (while waiting to develop automatic refresh)
go manually re-enable the Content Blockers in the Settings
I'm missing something somewhere (maybe a thread issue). Hopefully someone will be able to help!
Most likely, it means that you have rules that contains incorrect syntax or rules file have more the 50 000 rules.
Also the error can happened when you fill returningItems with nil context.completeRequest(returningItems: nil, completionHandler: nil) in your NSExtensionRequestHandling where context is NSExtensionContext
Related
I'm very new to swift and xcuitest. I recently came across addUIInterruptionMonitor for handling alerts that can pop up. What I would like to know is how I can verify that the alert happened and that the handler was dealt with. Take the following for example
addUIInterruptionMonitorWithDescription("Location Services") {
(alert) -> Bool in
alert.buttons["Allow"].tap()
return true
}
app.buttons["Request Location"].tap()
app.tap() // need to interact with the app again for the handler to fire
// after this if the handler never gets called I want the test to fail
I would like to test that the alert actually happens, but as far as I understand, after my last tap() if the alert never gets triggered my handler wont be called. I need to test that the alert actually happened and then maybe add some assertions to the contents of the handler
I seem to have answered my own question on further investigation. For asynchronous testing with xcuitest I can use a XCTestExpectation which will when created, cause the test to wait until the expectation is fulfilled, or fail after a certain timeout. With that my above code becomes:
let expectation = XCTestExpectation(description: "Location Service Alert")
let locationMonitorToken = addUIInterruptionMonitorWithDescription("Location Services") {
(alert) -> Bool in
alert.buttons["Allow"].tap()
expectation.fulfill() // test waits for this before passing
return true
}
app.buttons["Request Location"].tap()
app.tap() // alert triggered here
wait(for: [expectation], timeout: 3.0)
removeUIInterruptionMonitor(locationMonitorToken)
Update: forgot to put in the wait(for: [expectation], timeout: 3.0) after alert triggered to ensure handler is called.
I am getting an NSURLErrorDomain: -1003 while I am running my application in Xcode. I haven't seen this error on StackOverflow, any clue about this?
I am using Alamofire 4
func fetchAllPosts() {
Alamofire.request("http://www.somthing.com/wp-json/wp/v2/posts?categories_exclude=9").responseJSON
{ response in
if let data = response.data {
do {
let newPosts = try JSONDecoder().decode(Posts.self, from: data)
self.posts = newPosts.items
// Success
self.fetchAllPostsDidSucceed()
print("number of posts loaded: \(newPosts.items.count)")
}
[17655:2080758] [] nw_proxy_resolver_create_parsed_array PAC evaluation error: NSURLErrorDomain: -1003
-1003 is NSURLErrorCannotFindHost.
If you ever need to look up a NSURLError code in the future, press shift+command+o (the letter “oh”) in Xcode, search for NSURLError, unselect the “Swift” toggle in the upper right corner of the search box and choose/open NSURLError.h, and you’ll see all the codes that header file.
This particular error can be caused by any of a number of issues. For example, if this is a macOS app, you may want to go to your target settings, click on the “Capabilities” tag and make sure that “Outgoing Connections (Client)” is selected.
I want to test my macOS application. It uses your Macbook's camera, and want to handle this in my UITest. However I cannot get it working. Here is my NOT working code. This code triggers to notification, and I'm presented an alert to allow access to my camera, but the closure is not getting called. Thanks fo any help.
There are many solutions for iOS, but I need it on macOS.
let alertHandler = addUIInterruptionMonitor(withDescription: "Camera Permission Alert") { (alert) -> Bool in
if alert.buttons.matching(identifier: "OK").count > 0 {
alert.buttons["OK"].click()
self.app.click()
return true
} else {
return false
}
}
XCTAssertTrue(startButton.waitForExistence(timeout: 1.0))
startButton.click()
XCTAssertTrue(recordButton.waitForExistence(timeout: 20.0))
recordButton.click()
wait(for: 8)
recordButton.click()
removeUIInterruptionMonitor(alertHandler)
}
I managed to make interruption monitor work on macOS by adding an extra interaction after the interaction that triggers the system dialog (be it camera access or else). So in your example I would add an action after startButton.click() (if that is what triggers the camera access dialog).
Example:
func testCamera() {
let alertHandler = addUIInterruptionMonitor(withDescription: "Camera Permission Alert") { (alert) -> Bool in
if alert.buttons.matching(identifier: "OK").count > 0 {
alert.buttons["OK"].click()
self.app.click()
return true
} else {
return false
}
}
useCameraButton.click()
// try to interact with the app by clicking on the app's window
app.windows.first().click()
// at this point the handler should intercept the system interruption
// and blocks further execution until handler does return
// try to use the camera again
useCameraButton.click()
removeUIInterruptionMonitor(alertHandler)
}
Hint about this behaviour in Apple's documentation:
When an alert or other modal UI is an expected part of the
test workflow, don't write a UI interruption monitor. The test won’t
use the monitor because the modal UI isn’t blocking the test. A UI
test only tries its UI interruption monitors if the elements it needs
to interact with to complete the test are blocked by an interruption
from an unrelated UI.
https://developer.apple.com/documentation/xctest/xctestcase/handling_ui_interruptions
Basically, I want to upload some images before I run other functions that rely on the image being uploaded. I think I may have a misunderstanding of what GCD is/how the threads work. I want function 1 and 2 to happen AFTER I upload the images. They are both quick to execute but rely heavily on the upload images to be complete. Maybe I shouldn't use GCD (as I want to implement a waiting indicator)? I just can't seem to get this to execute properly
if goToHome {
DispatchQueue.global().async {
DispatchQueue.main.sync {
self.uploadImages() // Uploads the images, takes a good amount of time to execute
function1()
function2()
}
}
Functions 1 and 2 keep running before the upload images get completed as they take much less time to execute.
The basic pattern in Swift is to do work such as uploading on a background thread, then call a completion function on the main thread and continue work based on whether or not your upload finished successfully.
Generally you'll call back onto the main thread in case you need to do something with the user interface such as setting a progress indicator (which has to happen on the main thread).
So something like this:
func uploadInBackground(_ images: [Image], completion: #escaping (_ success: Bool) -> Void) {
DispatchQueue.global(qos: .background).async {
var success = true
// upload the images but stop on any error
for image in images {
success = uploadImage(image) // upload your images somehow
guard success else { break }
}
DispatchQueue.main.async {
completion(success)
}
}
}
func mainThreadUploader() {
let images = [Image(), Image()] // get your images from somewhere
// we are on the main thread where UI operations are ok, so:
startProgressIndicator()
uploadInBackground(images) { success in
// this callback is called on the main thread, so:
stopProgressIndicator()
guard success else { return }
// images uploaded ok, so proceed with functions that depend on
// the upload(s) completing successfully:
function1()
function2()
}
}
Despite running the upload image function in the main queue, the upload image function itself is running operations in a background queue. To fix this, possible strategies would be:
use a completion handler with the image upload, likely the easiest to implement depending on the implementation of the self.uploadImages() function
make the image uploading happen on the main thread, which is likely hard to implement and inadvisable
use a dispatch group, which I personally have less experience with but is an option
I'm trying to save an image to the photo library in Swift 3 (I'm working with Xcode 8).
ViewController Code:
func shareImage(image: UIImage) {
let items = [image]
var activityVC: UIActivityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
let excludeActivities: [UIActivityType] = [UIActivityType.airDrop,
UIActivityType.assignToContact,
UIActivityType.addToReadingList,
UIActivityType.copyToPasteboard]
activityVC.excludedActivityTypes = excludeActivities
self.present(activityVC, animated: true, completion: nil)
}
When I run the application, and click on the button to take the screenshot (converting it to image, ..., that's all working perfectly), the app asks for permission to access the photo library, I tap the "OK" button, and then the app crashes. The image is not saved in the photo library.
The only clue I get from Xcode is the following:
2016-09-28 11:24:27.216043 Ajax Kids[4143:1545362] [error] error: -addPersistentStoreWithType:SQLite configuration:(null) URL:file:///var/mobile/Media/PhotoData/Photos.sqlite?readonly_shm=1 options:{
NSPersistentStoreFileProtectionKey = NSFileProtectionCompleteUntilFirstUserAuthentication;
NSReadOnlyPersistentStoreOption = 1;
NSSQLitePersistWALOption = 1;
NSSQLitePragmasOption = {
"journal_mode" = WAL;
};
} ... returned error Error Domain=NSCocoaErrorDomain Code=256 "The file couldn’t be opened." UserInfo={reason=Failed to access file: 1} with userInfo dictionary {
reason = "Failed to access file: 1";
}
2016-09-28 11:24:27.216433 Ajax Kids[4143:1545362] [Migration] Unexpected error opening persistent store <private>, cannot attempt migration <private>)
2016-09-28 11:24:27.216568 Ajax Kids[4143:1545362] [Migration] Failed to open store <private>. Requires update via assetsd (256: <private>)
Does anyone have any idea how to fix this?
Thanks in advance!
UPDATE
Sharing the image on Social Media works fine, so the problem is specified to saving the image in the photo library.
Add new records in your new InfoPlist.strings file.
<key>NSPhotoLibraryAddUsageDescription</key>
<string>$(PRODUCT_NAME)</string>
UPD: iOS 11 key
On iOS 11, there is a new property called NSPhotoLibraryAddUsageDescription, similar to NSPhotoLibraryUsageDescription. See https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html
Try to force request permissions like this:
PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
//do things
}
}
do not forget import Photos. Hope this helps.
I found the culprit in my particular case. We are using Leanplum for analytics and push notifications. The Leanplum.syncResourcesAsync method was causing the opening of the photo library to crash. It took a couple of days to find as I wasn't aware Leanplum was doing anything to hook into a user's photo library... which in itself is concerning.
We weren't using the functionality this particular method brings, so were able to just remove the method call and the photo library stopped crashing.