Load video in share extension with Swift 4 - ios

I'm working on an iOS app that provides a share extension supposed to upload a video from Photo Library to a server.
I cannot find any example on how to handle the video.
Here is my code:
if itemProvider.hasItemConformingToTypeIdentifier("public.movie") {
print ("A movie has been chosen")
itemProvider.loadItem(forTypeIdentifier: "public.movie", options: nil, completionHandler: { (data, error) in
print ("Loaded the video")
if let video = data as? Data {
// Do stuff with the movie now.
print ("The movie should be loaded now")
}
self.extensionContext?.completeRequest(returningItems: [], completionHandler:nil)
})
}
The first print is printed, so I'm actually in the case of a public.movie item. But not the second and third print.
Can someone, please, tell me how the movie is passed and how I can handle it?
Thanks

With the help of app groups you can do work around : http://www.atomicbird.com/blog/sharing-with-app-extensions
Get video from library and save in Data in userdefaults.
Transfer the USerDefault key to via appschems and intercept it open URL method in app delgate of app.
With the same key load that video and forward it to server.
For references of app groups and Share extension : https://medium.com/#abhishekthaplithapliyal/ios-share-extension-930ba1ad3c3d

loadItem runs asynchronously, and when you're working with a video file, the file size is much larger than images, so loadItem does not have time to complete itself before self.extensionContext?.completeRequest runs and closes the share extension.
The solution I found involved the following:
(1) Create a boolean variable that measures whether the loadItem function is complete, or not.
var loadItemDone: Bool = false
(2) Write a recursive function that periodically checks to see whether the loadItem function has completed. If so, then run the completeRequest call. If not, continue recursion. In my case, I check for completion once per second, which is why it says "after: 1".
func waitForLoadItemToComplete(after seconds: Int) {
let deadline = DispatchTime.now() + .seconds(seconds)
DispatchQueue.main.asyncAfter(deadline: deadline) {
if self.loadItemDone == false{
self.waitForLoadItemToComplete(after: 1)
}else{
let alert = UIAlertController(title: "Success!", message: "Your file is uploaded!", preferredStyle: .alert)
let action1 = UIAlertAction(title: "Yeah!", style: .cancel) { (action) in
print("User acknowledged file uploaded.")
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
alert.addAction(action1)
self.present(alert, animated: true, completion: nil)
}
}
}
(3) Set the loadItemDone variable to true, when loadItem completes:
if attachment.hasItemConformingToTypeIdentifier("com.apple.quicktime-movie") {
// Do stuff with the movie now.
self.loadItemDone = true
}
'public.movie' didn't work for me. I had to use 'com.apple.quicktime-movie'
Also, DispatchQueue did not work for me, which is why I had to hack a bit... I would love to hear a better solution from anyone more familiar with threads in share extensions...

Related

Handling the answer from API in UI Testing Swift

I have weather app. It fetches the data from API. I enter needed city, then next screen opens and shows me the name of the city and temperature. I am writing UI test, which should open the app, handle an alert which asks to use location, then test should write the city name and check if this city exists in the screen. All works except checking the city name at the end. I thought maybe the problem is because it needs some time to get the answer from API, and tests doesn’t wait for it. Maybe I need to set timer to wait for answer. Or the problem is in smth else?
Here is my code and it fails at the last line.
func testExample() throws {
let app = XCUIApplication()
app.launchArguments = ["enable-testing"]
app.launch()
app/*#START_MENU_TOKEN#*/.staticTexts["My location"]/*[[".buttons[\"My location\"].staticTexts[\"My location\"]",".staticTexts[\"My location\"]"],[[[-1,1],[-1,0]]],[0]]#END_MENU_TOKEN#*/.tap()
addUIInterruptionMonitor(withDescription: "Allow “APP” to access your location?") { (alert) -> Bool in
let button = alert.buttons["Only While Using the App"]
if button.exists {
button.tap()
return true // The alert was handled
}
return false // The alert was not handled
}
app.textFields["Enter your city"].tap()
app.textFields["Enter your city"].typeText("Barcelona")
app.buttons["Check weather"].tap()
XCTAssertTrue(app.staticTexts["Barcelona"].exists)
}
XCTest comes with a built-in function you need
Documentation: https://developer.apple.com/documentation/xctest/xcuielement/2879412-waitforexistence/
Example:
XCTAssertTrue(myButton.waitForExistence(timeout: 3), "Button did not appear")
I found the function and used it to wait before the result.
Here is the function and its usage in my code.
func waitForElementToAppear(_ element: XCUIElement) -> Bool {
let predicate = NSPredicate(format: "exists == true")
let expectation = expectation(for: predicate, evaluatedWith: element,
handler: nil)
let result = XCTWaiter().wait(for: [expectation], timeout: 5)
return result == .completed
}
app.textFields["Enter your city"].tap()
app.textFields["Enter your city"].typeText("Barcelona")
app.buttons["Check weather"].tap()
let result = app.staticTexts["Barcelona"]
waitForElementToAppear(result)
XCTAssertTrue(result.exists)

In Xcode UI Test, how can I repeatedly check if an element exists and if so perform an action?

I’m implementing UI tests. The app makes API calls that could make alerts ( it's a UIView attached to the window ) appear. Of course, these are random/not predictable. If they show up, I have to dismiss them (clicking on the close button). Any idea how to do this? Do I have some event that says that something happened on the UI? I was thinking to have a thread that executes every 0.5 seconds that checks if the dismiss button exists and if so I tap on it.
DispatchQueue.global().async {
while true
{
DispatchQueue.main.async {
if(self.app.buttons["NotificationCloseButton"].exists)
{
self.app.buttons["NotificationCloseButton"].tap()
}
}
sleep(5)
}
}
The problem with this is that it causes random crashes: Neither attributes nor error returned
There is nice example of how to wait for element to appear on screen here. Here is example of code taken from the link:
let nextGame = self.app.staticTexts["Game 4 - Tomorrow"]
let exists = NSPredicate(format: "exists == true")
expectation(for: exists, evaluatedWithObject: nextGame, handler: nil)
app.buttons["Load More Games"].tap()
waitForExpectations(timeout: 5, handler: nil)
XCTAssert(nextGameLabel.exists)
Link also provides how to wait for system alert to appear:
addUIInterruptionMonitor(withDescription: "Location Dialog") { (alert) -> Bool in
alert.buttons["Allow"].tap()
return true
}
app.buttons["Find Games Nearby?"].tap()
app.tap() // need to interact with the app for the handler to fire
XCTAssert(app.staticTexts["Authorized"].exists)

Set rating right in the App (Swift 3, iOS 10.3)

I have a menu-button in my app. If user clicks this button he sees UIAlertView which include app-link to the App Store.
Here is the code:
#IBAction func navButton(_ sender: AnyObject) {
let alertController = UIAlertController(title: "Menu", message: "Thanks for using our app!", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Rate Us on the App Store", style: .default, handler: { (action: UIAlertAction) in
print("Send user to the App Store App Page")
let url = URL(string: "itms-apps://itunes.apple.com/app/id")
if UIApplication.shared.canOpenURL(url!) == true {
UIApplication.shared.openURL(url!)
}
}))
I know that in iOS 10.3 there was an opportunity to set a rating right in the application. What should I change, so that when a user clicks on a link in UIAlertView, he could set a rating right in the application?
I found some information on Apple Developer website (https://developer.apple.com/reference/storekit/skstorereviewcontroller) but I don't know how to do this in my app.
It's one class function based on looking at the docs.
SKStore​Review​Controller.requestReview()
It also states you shouldn't call this function dependent on a user pressing a button or any other type of action because it is not guaranteed to be called. It would be a bad user experience if you indicate they are about to be shown a review modal and then nothing appears.
If you use this new option in your app it seems the best option is to just place it somewhere that won't interrupt any important actions being conducted by the user and let the framework do the work.
You can use criteria the user isn't aware of to choose when to call the function, i.e. launched the app x amount of times, used x number of days in a row, etc.
Edit: alternative
If you want to keep more control over the ability to request reviews you can continue the old way and append the following to your store URL to bring them directly to the review page.
action=write-review
guard let url = URL(string: "appstoreURLString&action=write-review") else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil)

swift share with function on completion

My app can share files with other apps, but the problem is that I need to delete the files after sharing them... I tried using the onCompletion function as below:
let activityVC = UIActivityViewController(activityItems: objects, applicationActivities: nil)
view.present(activityVC, animated: true) {
try! FileManager.default.removeItem(at: targetURL)
}
The problem is that the onCompletion function executes after the action view disappears not after the whole process of sharing is finished, that's why if I delete the file and the sharing process is still ongoing it will be aborted.. an example is when using telegram for sharing; since telegram asks you to select a contact to send the file to, by that time the view has already disappeard (the function is executed and deleted the file before sharing it)...
It's far too soon to do anything in the completion handler of presenting the controller.
Set the completionWithItemsHandler property of the UIActivityViewController. This will get called when the sharing process is complete.
activityVC.completionWithItemsHandler = { (activityType: UIActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) -> Void in
if completed == true {
try! FileManager.default.removeItem(at: targetURL)
}
}

safariViewController debugging - URL not loading

I'm using SFSafariViewController in an iOS 9 app I'm building using Swift 2.
I am trying to open a URL which fails for some reason. EVery other URL I've tried works, except for this one:
http://www.ctvnews.ca/sci-tech/33-engineers-to-be-recognized-at-sci-tech-oscars-1.2730223
The URL is fine in regular mobile Safari in the simulator, on my iPhone, my iPad, and in any desktop browser. It is just when I try accessing it via Swift in this app that it fails.
Code is as follows:
func openInSafari(URLtoOpen: String) {
print("Trying to openURL: " + URLtoOpen)
let safariVC = SFSafariViewController(URL:NSURL(string: URLtoOpen)!, entersReaderIfAvailable: false)
safariVC.delegate = self
self.presentViewController(safariVC, animated: true, completion: nil)
}
func safariViewController(controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
// SFSafariViewController will present an error message in the browser,
// but in this case we will dismiss the view controller and present our
// own error alert.
if didLoadSuccessfully == false {
controller.dismissViewControllerAnimated(true, completion: { [unowned self] () -> Void in
let alert = UIAlertController(title: "Could Not Load", message: "The URL could not be loaded.", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Okay", style: .Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
})
}
}
This code works fine, and, as I said, other URLs load just fine. What I really need is just a way to more verbosely debug what safariViewController is encountering that is causing it to fail.
The didLoadSuccessfully == false line doesn't really appear to offer much more debugging options to get a sense of what went wrong.
In other words, how do I debug this? I can't seem to find anything in Apple's docs that would explain what to check in case of a loading error.
Please help!
Thanks.
The SFSafariViewController is voluntarily shielded from the app (I wouldn't be surprised if it's actually a different process), and there's very little information shared with the app for privacy and security reasons.
What you could try is loading the same based in an UIWebView or WKWebView (which gives you a lot more feedback, but doesn't share cookies or passwords with Safari) to see what happens then.

Resources