UIDocumentInteractionController Open Menu Cancelled Callback - ios

I am currently developing an application specifically for iOS7 that utilizes UIDocumentInteractionController open in menu and need a method that notifies me when a user cancels and does not choose an available option.
UIDocumentInteractionControllerDelegate offers:
- (void)documentInteractionControllerDidDismissOptionsMenu:(UIDocumentInteractionController *) controller
but this does not specify whether the user tapped one of the available options or cancel.
Any ideas?

NOTE: This will not work for iOS 8 anymore, only iOS7 and earlier
To determine whether the user has canceled the menu or selected an option, you have to make use of the following delegate methods:
1-
- (void)documentInteractionController:(UIDocumentInteractionController *)controller
didEndSendingToApplication:(NSString *)application
{
//get called only when the user selected an option and then the delegate method bellow get called
// Set flag here _isOptionSelected = YES;
_isOptionSelected = YES;
}
2-
- (void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller
{
//called whether the user has selected option or not
// check your flag here
if(_isOptionSelected == NO) {
//the user has canceled the menu
}
_isOptionSelected = NO;
}
iOS 8
For iOS 8 and above, use this method instead of the one in step 2:
- (void)documentInteractionController:(UIDocumentInteractionController *)controller
didEndSendingToApplication:(NSString *)application

This will work on iOS7 && iOS8
BOOL didSelectOptionFromDocumentController = NO;//**set this to "NO" every time you present your documentInteractionController too
-(void)documentInteractionController:(UIDocumentInteractionController *)controller willBeginSendingToApplication:(NSString *)application {
didSelectOptionFromDocumentController = YES;
}
-(void)documentInteractionControllerDidDismissOpenInMenu:(UIDocumentInteractionController *)controller {
if (didSelectOptionFromDocumentController == NO) {//user cancelled.
}
}

This works for iOS8 & iOS9 for 3rd party apps AND System Apps!
It's not pretty but it works.
Can anyone tell me if this will pass App Review? Not sure as I'm referring to a class name which is not publicly accessible (_UIDocumentActivityViewController).
This is Swift 2.2!
NSObject Extension to get a string of the class name:
extension NSObject {
var theClassName: String {
return NSStringFromClass(self.dynamicType)
}
}
Your Viewcontroller where you're calling the UIDocumentInteractionController from:
var appOpened = false
var presentedVCMonitoringTimer: NSTimer!
var docController: UIDocumentInteractionController!
func openDocController() {
docController = UIDocumentInteractionController(URL: yourURL!)
docController.UTI = "your.UTI"
docController.delegate = self
docController.presentOptionsMenuFromRect(CGRectZero, inView: self.view, animated: true)
// Check the class of the presentedViewController every 2 seconds
presentedVCMonitoringTimer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: #selector(self.checkPresentedVC), userInfo: nil, repeats: true)
}
func checkPresentedVC() {
if let navVC = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController {
print(navVC.presentedViewController?.theClassName)
if navVC.presentedViewController != nil && (navVC.presentedViewController?.theClassName)! != "_UIDocumentActivityViewController" && (navVC.presentedViewController?.theClassName)! != self.theClassName {
// A system App was chosen from the 'Open In' dialog
// The presented ViewController is not the DocumentInteractionController (anymore) and it's not this viewcontroller anymore (could be for example the MFMailComposeViewController if the user chose the mail app)
appOpened = true
presentedVCMonitoringTimer?.invalidate()
presentedVCMonitoringTimer = nil
}
}
}
func documentInteractionControllerDidDismissOptionsMenu(controller: UIDocumentInteractionController) {
print("dismissedOptionsMenu")
presentedVCMonitoringTimer?.invalidate()
presentedVCMonitoringTimer = nil
if appOpened {
// Do your thing. The cancel button was not pressed
appOpened = false
}
else {
// Do your thing. The cancel button was pressed
}
}
func documentInteractionController(controller: UIDocumentInteractionController, willBeginSendingToApplication application: String?) {
// A third party app was chosen from the 'Open In' menu.
appOpened = true
presentedVCMonitoringTimer?.invalidate()
presentedVCMonitoringTimer = nil
}

For Swift 4, use this:
func documentInteractionControllerDidDismissOpenInMenu(_ controller: UIDocumentInteractionController) {
// this function get called when users finish their work,
// either for sharing thing within the same app or exit to other app will do
}
I use it when after users have shared image to Facebook and Instagram.

Related

AdMob - Get Notify when isReady property changes

I trying implement Rewarded Ad - Rewarded Ads New APIs (Beta). Video is load and isReady property is changing to true in a couple of seconds.
I have a button on which user press and Rewarded Video appear
This is function which is fire when user press on button
func presentRewardAd(from viewController: UIViewController) {
if rewardedAd.isReady {
rewardedAd.present(fromRootViewController: viewController, delegate: self)
}
}
The problem is
I want to hide button until video isReady == true, and when it's ready show button. So i want to get notify when rewardedAd.isReady is changing.
What i try so far:
class CustomRewardAd: GADRewardedAd {
private var observation: NSKeyValueObservation?
override init(adUnitID: String) {
super.init(adUnitID: adUnitID)
observation = observe(\.isReady, options: [.old, .new]) { object, change in
print("isReady changed from: \(change.oldValue!), updated to: \(change.newValue!)")
}
}
}
Also i tried this Using Key-Value Observing in Swift but same result.
But changeHandler never gets called. Am i doing something wrong?
Thanks!
I found solution, not ideal but it's works! Maybe this is can help someone in future.
When new rewarded request finishes, isReady property is set to true or false depends what response is.
private func createAndLoadRewardedAd() -> GADRewardedAd {
let rewardedAd = GADRewardedAd(adUnitID: "ca-app-pub-3940256099942544/1712485313")
rewardedAd.load(GADRequest()) { [weak self] error in
guard let self = self else { return }
self.videoIsReady?(rewardedAd.isReady) // already set
}
return rewardedAd
}
You are welcome!

how can I handle properly the behavior of camera in my Swift app?

In my swift app I'm allowing users to take photos.
For that purpose I've decided to use CameraManager from here: https://github.com/imaginary-cloud/CameraManager
When user opens my app, he sees a button - when he presses it, the camera view appears and he can take a photo. He also can dismiss the camera view and later on, at some point, press the button one more time to open camera view again.
If I understand it correctly from the plugin docs, I need to add a camera view to my view during first usage, then - in case of dismiss - invoke stopCaptureSession(), and during every next usage call resumeCaptureSession().
Currently in my swift code I have three methods:
let cameraManager = CameraManager()
fileprivate func addCameraToView()
{
cameraManager.addPreviewLayerToView(cameraView, newCameraOutputMode: CameraOutputMode.stillImage)
}
fileprivate func stopCaptureSession() {
cameraManager.stopCaptureSession()
}
fileprivate func resumeCaptreSession() {
cameraManager.resumeCaptureSession()
}
The IBAction for the button has the following code:
let currentCameraState = cameraManager.currentCameraStatus()
if currentCameraState == .notDetermined {
cameraManager.askUserForCameraPermission({ permissionGranted in
if permissionGranted {
self.resumeCaptreSession()
}
})
} else if (currentCameraState == .ready) {
self.resumeCaptreSession()
} else {
print("we do not have access to camera")
}
and in the IBAction for the dismiss button I had:
print("cancelling camera")
stopCaptureSession()
To make it work properly, I need to call addCameraToView() somewhere earlier - until now I was adding it in viewDidLoad, but I realized that I cannot do that because while doing so - the camera stays active until user presses the dismiss button.
So I thought about changing my code in IBAction for the camera button and add a camera from there. However, I have to add it only in case it wasn't add before - in the other case I need to call resumeCaptureSession().
The problem is that in CameraManager the function responsible for adding camera to the view is declared like this:
open func addPreviewLayerToView(_ view: UIView, newCameraOutputMode: CameraOutputMode) -> CameraState {
return addLayerPreviewToView(view, newCameraOutputMode: newCameraOutputMode, completion: nil)
}
open func addLayerPreviewToView(_ view: UIView, newCameraOutputMode: CameraOutputMode, completion: ((Void) -> Void)?) -> CameraState {
if _canLoadCamera() {
if let _ = embeddingView {
if let validPreviewLayer = previewLayer {
validPreviewLayer.removeFromSuperlayer()
}
}
if cameraIsSetup {
_addPreviewLayerToView(view)
cameraOutputMode = newCameraOutputMode
if let validCompletion = completion {
validCompletion()
}
} else {
_setupCamera({ Void -> Void in
self._addPreviewLayerToView(view)
self.cameraOutputMode = newCameraOutputMode
if let validCompletion = completion {
validCompletion()
}
})
}
}
return _checkIfCameraIsAvailable()
}
and resumeCaptureSession() is defined like this:
open func resumeCaptureSession() {
if let validCaptureSession = captureSession {
if !validCaptureSession.isRunning && cameraIsSetup {
validCaptureSession.startRunning()
_startFollowingDeviceOrientation()
}
} else {
if _canLoadCamera() {
if cameraIsSetup {
stopAndRemoveCaptureSession()
}
_setupCamera({Void -> Void in
if let validEmbeddingView = self.embeddingView {
self._addPreviewLayerToView(validEmbeddingView)
}
self._startFollowingDeviceOrientation()
})
}
}
}
So my question is - when user opens camera view, how can I check if camera was added to the view before, and if it was added - call resumeCaptureSession(), otherwise do not call it and just leave it with calling addCameraToView?

SKStoreProductViewController's cancel button leaving a white, blank screen

I made a quick sample project with an Action Extension for iOS 9. It works properly in my containing app, but it does NOT in my extension. I used the same code in both view controllers.
The button that presents a store product view controller:
#IBAction func storebutton(sender: AnyObject) {
let storeViewController = SKStoreProductViewController()
let parameters = [SKStoreProductParameterITunesItemIdentifier: NSNumber(integer: 377298193)]
storeViewController.delegate = self
storeViewController.loadProductWithParameters(parameters, completionBlock: nil)
self.presentViewController(storeViewController, animated: true) { () -> Void in }
}
Delegate Method:
//MARK: SKStoreProductViewController Delegate
func productViewControllerDidFinish(viewController: SKStoreProductViewController) {
print("Delegate")
viewController.dismissViewControllerAnimated(true,completion: nil)
}
When I tap the Cancel button on the Store Product View that was from my Action Extension, the store view is dismissed.
But its delegate method never gets called and the whole screen remains totally white - never gets back to Action Extension's view.
Can anyone help me please?
Never mind.
I found out the delegate method was outside of the Class.
I did not know Xcode does not warn about it.
it happens when the SKStoreProductViewController has no delegate set.
SKStoreProductViewController * vc = [SKStoreProductViewController new];
[vc loadProductWithParameters:#{SKStoreProductParameterITunesItemIdentifier:#"00000"} completionBlock:^(BOOL result, NSError * _Nullable error) {
}];
vc.delegate = self;

UIMenuController doesn't update menu for first time

I have UITextView on which I want to add highlight as custom menu item. I have registered to following notification UIMenuControllerWillShowMenuNotification.
The method for the notification is something like this:
if textIsHighlighted {
let highlightMenuItem = UIMenuItem(title: "Highlight", action: Selector("highlightText"))
UIMenuController.sharedMenuController().menuItems = [highlightMenuItem]
}
else {
let highlightMenuItem = UIMenuItem(title: "Dehighlight", action: Selector("highlightText"))
UIMenuController.sharedMenuController().menuItems = [highlightMenuItem]
}
Although the first time the menucontroller fails to update even though it executes the part of code. It shows the last value. Where should I write this part of code as I feel that during willShow menuController it's already created and thus fails to update.
Hopefully you've solved this by now, but I've just figured this one out myself:
Other answers have said you can update the menu items by adding it when the UIMenuControllerWillShowMenuNotification is called, but this wasn't working for me (iOS 9, Swift 2).
Instead I implemented the UITextView delegate method: textViewDidChangeSelection and set the relevant menu items there:
func textViewDidChangeSelection(textView: UITextView) {
if self.currentSelectionIsInHighlightedRange() {
self.setUpUnhighlightMenuItem()
} else {
self.setUpHighlightMenuItem()
}
}
private func currentSelectionIsInHighlightedRange() -> Bool {
let allHighlightedRanges = self.document.highlightedRanges()
let selectedTextRange = self.documentView.textView.selectedRange
for range in allHighlightedRanges {
let intersectionRange = NSIntersectionRange(range, selectedTextRange)
if intersectionRange.length > 0 {
return true
}
}
return false
}

Present GameCenter authenticationVC again

I'm facing a little issue here and I hope someone will help me figure out what is wrong.
*The test project presented below can be find here : http://goo.gl/wz84aA (FR) or https://goo.gl/0m8LrZ (Mega.NZ) *
I'm trying to present to the user the authentification view controller proposed by apple for the GameCenter feature. More precisely, re-present it if he canceled it on the first time.
I have a game with a storyboard like that :
GameNavigationController :
class GameNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("showAuthenticationViewController"), name: PresentAuthenticationViewController, object: nil)
GameKitHelper.sharedInstance.authenticateLocalPlayer()
}
func showAuthenticationViewController() {
let gameKitHelper = GameKitHelper.sharedInstance
if let authenticationViewController = gameKitHelper.authenticationViewController {
self.topViewController.presentViewController(authenticationViewController, animated: true, completion: nil)
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
MenuViewController :
class MenuViewController: UIViewController {
#IBAction func didTapLeaderboardBTN() {
// TRY 2
//if ( !GameKitHelper.sharedInstance.gameCenterEnabled) {
GameKitHelper.sharedInstance.authenticateLocalPlayer()
//} else {
GameKitHelper.sharedInstance.showGKGameCenterViewController(self)
//}
}
}
GameKitHelper :
import GameKit
import Foundation
let PresentAuthenticationViewController = "PresentAuthenticationViewController"
let singleton = GameKitHelper()
class GameKitHelper: NSObject, GKGameCenterControllerDelegate {
var authenticationViewController: UIViewController?
var lastError: NSError?
var gameCenterEnabled: Bool
class var sharedInstance: GameKitHelper {
return singleton
}
override init() {
gameCenterEnabled = true
super.init()
}
func authenticateLocalPlayer () {
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = { (viewController, error) in
self.lastError = error
if viewController != nil {
self.authenticationViewController = viewController
NSNotificationCenter.defaultCenter().postNotificationName(PresentAuthenticationViewController, object: self)
} else if localPlayer.authenticated {
self.gameCenterEnabled = true
} else {
self.gameCenterEnabled = false
}
}
}
func showGKGameCenterViewController(viewController: UIViewController!) {
if ( !self.gameCenterEnabled ) {
println("Local player is not authenticated")
// TRY 1
//self.authenticateLocalPlayer()
return
}
let gameCenterViewController = GKGameCenterViewController()
gameCenterViewController.gameCenterDelegate = self
gameCenterViewController.viewState = .Leaderboards
viewController.presentViewController(gameCenterViewController, animated: true, completion: nil)
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController!) {
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
}
What is currently working :
if the user is previously logged-in (within the GameCenter app) then he's able to open the leaderboard view ;
if the user wasn't logged-in then he's prompted to log-in when the game navigation controller is loaded (and then open the leaderboard).
What is NOT currently working :
if he cancel three time the authentification, then the authentification won't appear anymore (even at launch) ; // Apparently a known "problem", not "fixable"
if the user cancel his authentification, when he tries to load the leaderboard the authentification won't appear again.
I tried 2-3 things as you can see in the commented code above, but none of them is working ; I can't make the authentification view appear again.
PS : My code is written in Swift, but help in Objective-C is welcomed as well.
As you have found out, if the Game Center authentication dialog is canceled 3 times, then you can't bring it back without resetting the device.
There is another "security feature" built into Game Center which does not allow an app to re-authenticate if the user has already canceled the dialog without leaving the app. So for your authentication dialog to show, the user must leave and then re-enter your app.
There is really no way around it. What I've done in a couple of projects is to display a message to the user:
Game Center not available. Please make sure you are signed in through the Game Center app
I will show that message after trying to authenticate and if Game Center isn't available or the user is not signed in.
If you want to be able to re-present this to your user then go to settings -> General -> Reset -> -> Reset Location & Privacy.
This will force iOS to forget preferences for apps for example whether they can use location services, send you push notifications and also game centre preferences. Keep in mind this will reset privacy settings for all apps.

Resources