How do I get a back button in pushed ViewController (Xcode) - objective-c

I have a viewController with a menu button set as navigationItem like this:
self.navigationItem.setLeftBarButton(leftButton, animated: false).
It brings up a drawer menu, which is fine in its context. But what if I want to push the viewController from elsewhere in the project where a back button is more appropriate instead of a menu button? That is, I just want to go back to the previous viewController instead of bringing up the menu. How do I get rid of the menu button and instead get the back button in that particular instance?
Here is the code for the class. As you can see, the navigationItem is set here.
override func viewDidLoad() {
super.viewDidLoad()
self.setUserInterfaceStyleLight()
loginStoryboard = UIStoryboard(name: "StoryboardOne", bundle: nil)
let leftButton = UIBarButtonItem(image: UIImage(named: "menu-icon"), style: .plain, target: self, action: #selector(self.leftSideMenuButtonPressed(_:)))
self.navigationItem.setLeftBarButton(leftButton, animated: false)
self.segmentController.tintColor = .white
if showContacts == true {
lastSegmentViewed = 1
}
Analytics.logEvent("contact_book", parameters: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDidLogin(_:)), name: NSNotification.Name.UserDidLogin, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationAndViewWasResumed(_:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
self.view.backgroundColor = ThemTemplate.shared.getThem().secondaryColor
if #available(iOS 13.0, *) {
segmentController.backgroundColor = UIColor.gray
} else {
segmentController.backgroundColor = UIColor.clear
}
}
Is it possible to hide and disable the navigationItem from the previous viewController or change it to a back button?
This is how I push the viewController:
UIStoryboard *containerStoryboard = [UIStoryboard storyboardWithName:#"Login" bundle:nil];
BaseViewController *v = [containerStoryboard instantiateViewControllerWithIdentifier:#"base"];
[self.navigationController pushViewController:v animated:YES];
In the class before the push to the new viewController I tried something like:
v.navigationItem.leftBarButtonItem = nil;
v.navigationItem.hidesBackButton = NO;
...which did nothing (and yes, setting it to nil is dumb, just wanted to see if something happened and it did not).
The viewController I'm pushing from is not embedded in a navigationController, if that helps. I tried embedding it on one, but with the same results.
Left me know if there is anything else that can be helpful.

Add a Bool var in BaseViewController:
class BaseViewController: UIViewController {
var bShowMenu: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
self.setUserInterfaceStyleLight()
loginStoryboard = UIStoryboard(name: "StoryboardOne", bundle: nil)
if bShowMenu {
let leftButton = UIBarButtonItem(image: UIImage(named: "menu-icon"), style: .plain, target: self, action: #selector(self.leftSideMenuButtonPressed(_:)))
self.navigationItem.setLeftBarButton(leftButton, animated: false)
}
self.segmentController.tintColor = .white
if showContacts == true {
lastSegmentViewed = 1
}
Analytics.logEvent("contact_book", parameters: nil)
NotificationCenter.default.addObserver(self, selector: #selector(userDidLogin(_:)), name: NSNotification.Name.UserDidLogin, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(applicationAndViewWasResumed(_:)), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
self.view.backgroundColor = ThemTemplate.shared.getThem().secondaryColor
if #available(iOS 13.0, *) {
segmentController.backgroundColor = UIColor.gray
} else {
segmentController.backgroundColor = UIColor.clear
}
}
}
then, when you instantiate the controller, tell it whether or not to use the "menu" button:
UIStoryboard *containerStoryboard = [UIStoryboard storyboardWithName:#"Login" bundle:nil];
BaseViewController *v = [containerStoryboard instantiateViewControllerWithIdentifier:#"base"];
// if you do NOT want the menu button
v.bShowMenu = false
[self.navigationController pushViewController:v animated:YES];

Related

Disable UIButton within IBAction Closure - Swift 4

When the user is playing my app, there are certain buttons that I do not want them to accidentally press. So, I would like to grey out and disable them when the app is in gameMode = 1. The following code disables the button when it is supposed to but then does not enable again it when I need it to (when it is in gameMode = 0). Also the button does not grey out.
#IBAction func menuButton(_ sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myVC = storyboard.instantiateViewController(withIdentifier: "viewController")
if gameMode == 0 {
sender.isEnabled = true
self.present(myVC, animated: false, completion: nil)
} else if gameMode == 1 {
sender.isEnabled = false
}
}
#IBAction func menuButton(_ sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myVC = storyboard.instantiateViewController(withIdentifier: "viewController")
if gameMode == 0 {
sender.isUserInteractionEnabled = true
self.present(myVC, animated: false, completion: nil)
} else if gameMode == 1 {
sender.isUserInteractionEnabled = false
}
}

CLLocationManager doesn't get called when returning from a different VC

CLLocationManager delegate doesn't get called when I return to a view from another view controller. But it works perfectly fine if the view controller loads without presenting another view controller.
For example, everytime if defaults.objectForKey("firstTimeUser") == nil { is called and that view controller is dismissed, CLLocationManager delegate doesn't get called.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
let defaults = NSUserDefaults.standardUserDefaults()
if defaults.objectForKey("firstTimeUser") == nil {
if let servicesController = self.storyboard?.instantiateViewControllerWithIdentifier("ServicesController") as? UITableViewController {
self.navigationController?.presentViewController(servicesController, animated: true, completion: nil)
}
defaults.setObject("set", forKey: "firstTimeUser")
defaults.synchronize()
} else {
getLocationData()
loadNewsData()
}
}
func getLocationData() {
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.distanceFilter = 3000
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
}
I believe it has something to do with LocationManager being called inside a class method rather than an instance method. Also tried setting LocationManager in the main_thread:
dispatch_async(dispatch_get_main_queue(), {
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.distanceFilter = 3000
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
})

Handle long press on a single UITabBarItem

I use the long press gesture on a tab bar. But I only need the long press gesture for one particular tab bar item.
How can I solve this problem? Could I customize the long press gesture in tab bar?
Here's how I did it using Swift 3:
protocol MyTabControllerProtocol: class {
func tabLongPressed()
}
class MyTabController: UITabBarController {
func viewDidLoad() {
super.viewDidLoad()
viewControllers = [
// add your view controllers for each tab bar item
// NOTE: if you want view controller to respond to long press, then it should extend MyTabControllerProtocol
]
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(astroButtonItemLongPressed(_:)))
tabBar.addGestureRecognizer(longPressRecognizer)
}
func astroButtonItemLongPressed(_ recognizer: UILongPressGestureRecognizer) {
guard recognizer.state == .began else { return }
guard let tabBar = recognizer.view as? UITabBar else { return }
guard let tabBarItems = tabBar.items else { return }
guard let viewControllers = viewControllers else { return }
guard tabBarItems.count == viewControllers.count else { return }
let loc = recognizer.location(in: tabBar)
for (index, item) in tabBarItems.enumerated() {
guard let view = item.value(forKey: "view") as? UIView else { continue }
guard view.frame.contains(loc) else { continue }
if let nc = viewControllers[index] as? UINavigationController {
if let vc = nc.viewControllers.first as? MyTabControllerProtocol {
vc.tabLongPressed()
}
} else if let vc = viewControllers[index] as? MyTabControllerProtocol {
vc.tabLongPressed()
}
break
}
}
}
You can subclass UITabBarController and add a UILongPressGestureRecognizer to it's tabBar. Acting as the delegate of the gesture recognizer will allow you to be selective over when it will detect a long press. Since the tab bar item will be selected as soon as the user touches it you can use the selectedItem property to perform this check.
#interface TabBarController () <UIGestureRecognizerDelegate>
#property (nonatomic, strong) UILongPressGestureRecognizer *longPressRecognizer;
#end
#implementation TabBarController
- (void)viewDidLoad {
[super viewDidLoad];
self.longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(recognizerFired:)];
self.longPressRecognizer.delegate = self;
[self.tabBar addGestureRecognizer:self.longPressRecognizer];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
// This will ensure the long press only occurs for the
// tab bar item which has it's tag set to 1.
// You can set this in Interface Builder or in code
// wherever you are creating your tabs.
if (self.tabBar.selectedItem.tag == 1) {
return YES;
}
else {
return NO;
}
}
- (void)recognizerFired:(UILongPressGestureRecognizer *)recognizer {
// Handle the long press...
}
#end
Here is a solution in swift 5 :
Add longpress Gesture recognizer to the "Entire" tabbar using storyboard or code..
and Don't forget to let your ViewController be its delegate .. and implement the delegate method below
to check if the incoming touch is inside "one" of your tabbar subViews .. if yes return true ,, else return false ..
here are the code that will let the recognizer fire only when we longPress on the first tab:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view?.isDescendant(of: tabBar.subviews[1]) == true {return true}
return false
}
Note: tabbar.subviews array count is number of the items + 1 which is the background of the tabbar .. so if you want the view of the first item you can fint it and index 1 not 0
I did this by getting the specific tabBarItem's view that user can interact and simply added the long press gesture to that. With that way you do not have to write any protocols or subclass the TabBarViewController.
let longPressGestureRecognizer = UILongPressGestureRecognizer.init(target: self, action: #selector(longTap(_:)))
longPressGestureRecognizer.minimumPressDuration = 1.0
self.tabBarController?.orderedTabBarItemViews()[0].addGestureRecognizer(longPressGestureRecognizer)
And as for getting the tabBarItemViews :
extension UITabBarController {
func orderedTabBarItemViews() -> [UIView] {
let interactionViews = tabBar.subviews.filter({$0.isUserInteractionEnabled})
return interactionViews.sorted(by: {$0.frame.minX < $1.frame.minX})
}
P.S. : The viewController, namely "self" is the first item for the tabBarController.
If you just need to recognize a long press on one of the tabBar items, you can do this in the corresponding viewController's viewDidLoad method:
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget: self action: #selector(handleLongPress:)];
[self.tabBarController.tabBar addGestureRecognizer: longPressGesture];
And then:
- (void)handleLongPress:(UILongPressGestureRecognizer *) recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
UITabBar *tabBar = ((UITabBar* )recognizer.view);
if (tabBar.selectedItem == self.tabBarItem) {
doSomethingVeryExciting();
}
}
}
This won't fire if you just switch tabs.

UIPopoverPresentationController on iOS 8 iPhone

Does anyone know if UIPopoverPresentationController can be used to present popovers on iPhones? Wondering if Apple added this feature on iOS 8 in their attempt to create a more unified presentation controllers for iPad and iPhone.
Not sure if its OK to ask/answer questions from Beta. I will remove it in that case.
You can override the default adaptive behaviour (UIModalPresentationFullScreen in compact horizontal environment, i.e. iPhone) using the
adaptivePresentationStyleForPresentationController: method available through UIPopoverPresentationController.delegate.
UIPresentationController uses this method to ask the new presentation style to use, which in your case, simply returning UIModalPresentationNone will cause the UIPopoverPresentationController to render as a popover instead of fullscreen.
Here's an example of the popover using a segue setup in storyboard from a UIBarButtonItem to "present modally" a UIViewController
class SomeViewController: UIViewController, UIPopoverPresentationControllerDelegate {
// override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) { // swift < 3.0
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PopoverSegue" {
if let controller = segue.destinationViewController as? UIViewController {
controller.popoverPresentationController.delegate = self
controller.preferredContentSize = CGSize(width: 320, height: 186)
}
}
}
// MARK: UIPopoverPresentationControllerDelegate
//func adaptivePresentationStyleForPresentationController(controller: UIPresentationController!) -> UIModalPresentationStyle { // swift < 3.0
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
// Return no adaptive presentation style, use default presentation behaviour
return .None
}
}
This trick was mentioned in WWDC 2014 session 214 "View Controller Advancement in iOS8" (36:30)
If anybody wants to present a popover with code only, you can use the following approach.
OBJECTIVE - C
Declare a property of UIPopoverPresentationController:
#property(nonatomic,retain)UIPopoverPresentationController *dateTimePopover8;
Use the following method to present the popover from UIButton:
- (IBAction)btnSelectDatePressed:(id)sender
{
UINavigationController *destNav = [[UINavigationController alloc] initWithRootViewController:dateVC];/*Here dateVC is controller you want to show in popover*/
dateVC.preferredContentSize = CGSizeMake(280,200);
destNav.modalPresentationStyle = UIModalPresentationPopover;
_dateTimePopover8 = destNav.popoverPresentationController;
_dateTimePopover8.delegate = self;
_dateTimePopover8.sourceView = self.view;
_dateTimePopover8.sourceRect = sender.frame;
destNav.navigationBarHidden = YES;
[self presentViewController:destNav animated:YES completion:nil];
}
Use the following method to present the popover from UIBarButtonItem:
- (IBAction)btnSelectDatePressed:(id)sender
{
UINavigationController *destNav = [[UINavigationController alloc] initWithRootViewController:dateVC];/*Here dateVC is controller you want to show in popover*/
dateVC.preferredContentSize = CGSizeMake(280,200);
destNav.modalPresentationStyle = UIModalPresentationPopover;
_dateTimePopover8 = destNav.popoverPresentationController;
_dateTimePopover8.delegate = self;
_dateTimePopover8.sourceView = self.view;
CGRect frame = [[sender valueForKey:#"view"] frame];
frame.origin.y = frame.origin.y+20;
_dateTimePopover8.sourceRect = frame;
destNav.navigationBarHidden = YES;
[self presentViewController:destNav animated:YES completion:nil];
}
Implement this delegate method too in your view controller:
- (UIModalPresentationStyle) adaptivePresentationStyleForPresentationController: (UIPresentationController * ) controller {
return UIModalPresentationNone;
}
To dismiss this popover, simply dismiss the view controller. Below is the code to dismiss the view controller:
-(void)hideIOS8PopOver
{
[self dismissViewControllerAnimated:YES completion:nil];
}
SWIFT
Use the following method to present the popover from UIButon:
func filterBooks(sender: UIButon)
{
let filterVC = FilterDistanceViewController(nibName: "FilterDistanceViewController", bundle: nil)
var filterDistanceViewController = UINavigationController(rootViewController: filterVC)
filterDistanceViewController.preferredContentSize = CGSizeMake(300, 205)
let popoverPresentationViewController = filterDistanceViewController.popoverPresentationController
popoverPresentationViewController?.permittedArrowDirections = .Any
popoverPresentationViewController?.delegate = self
popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem
popoverPresentationViewController!.sourceView = self.view;
popoverPresentationViewController!.sourceRect = sender.frame
filterDistanceViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
filterDistanceViewController.navigationBarHidden = true
self.presentViewController(filterDistanceViewController, animated: true, completion: nil)
}
Use the following method to present the popover from UIBarButtonItem:
func filterBooks(sender: UIBarButtonItem)
{
let filterVC = FilterDistanceViewController(nibName: "FilterDistanceViewController", bundle: nil)
var filterDistanceViewController = UINavigationController(rootViewController: filterVC)
filterDistanceViewController.preferredContentSize = CGSizeMake(300, 205)
let popoverPresentationViewController = filterDistanceViewController.popoverPresentationController
popoverPresentationViewController?.permittedArrowDirections = .Any
popoverPresentationViewController?.delegate = self
popoverPresentationController?.barButtonItem = self.navigationItem.rightBarButtonItem
popoverPresentationViewController!.sourceView = self.view;
var frame:CGRect = sender.valueForKey("view")!.frame
frame.origin.y = frame.origin.y+20
popoverPresentationViewController!.sourceRect = frame
filterDistanceViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
filterDistanceViewController.navigationBarHidden = true
self.presentViewController(filterDistanceViewController, animated: true, completion: nil)
}
Implement this delegate method too in your view controller:
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle{
return .None
}
Please make sure to add delegate UIPopoverPresentationControllerDelegate in .h/.m/.swift file
PROBLEM: iPhone popover displays fullscreen and does not respect preferredContentSize value.
SOLUTION: Contrary to what Apple suggests in the UIPopoverPresentationController Class reference, presenting the view controller after getting a reference to the popover presentation controller and configuring it.
// Get the popover presentation controller and configure it.
//...
// Present the view controller using the popover style.
[self presentViewController:myPopoverViewController animated: YES completion: nil];
Make sure to implement UIAdaptivePresentationControllerDelegate
like this:
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
return UIModalPresentationNone;
}
If you don't want full-screen popovers
I've found some workaround.
On Xcode6.1, use presentationController.delegate instead of popoverPresentationController.delegate.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier compare:#"showPopOver"] == NSOrderedSame) {
UINavigationController * nvc = segue.destinationViewController;
UIPresentationController * pc = nvc.presentationController;
pc.delegate = self;
}
}
#pragma mark == UIPopoverPresentationControllerDelegate ==
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller
{
return UIModalPresentationNone;
}
In WWDC 2014 "View Controller Advancements in iOS8", below codes can show popover on iPhone.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UINavigationController * nvc = segue.destinationViewController;
UIPopoverPresentationController * pvc = nvc.popoverPresentationController;
pvc.delegate = self;
}
#pragma mark == UIPopoverPresentationControllerDelegate ==
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller
{
return UIModalPresentationNone;
}
But On Xcode 6.1, these codes shows FullScreen presentation...
(nvc.popoverPresentationController is nil)
I doubt it might be an Apple's bug.
In iOS 8.3 and later, use the following syntax in the UIPopoverPresentationControllerDelegate protocol to override your popup's UIModalPresentationStyle.
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
You can extend the UIPopoverPresentationControllerDelegate like this:
protocol PopoverPresentationSourceView {}
extension UIBarButtonItem : PopoverPresentationSourceView {}
extension UIView : PopoverPresentationSourceView {}
extension UIPopoverPresentationControllerDelegate where Self : UIViewController {
func present(popover: UIViewController,
from sourceView: PopoverPresentationSourceView,
size: CGSize, arrowDirection: UIPopoverArrowDirection) {
popover.modalPresentationStyle = .popover
popover.preferredContentSize = size
let popoverController = popover.popoverPresentationController
popoverController?.delegate = self
if let aView = sourceView as? UIView {
popoverController?.sourceView = aView
popoverController?.sourceRect = CGRect(x: aView.bounds.midX, y: aView.bounds.midY, width: 0, height: 0)
} else if let barButtonItem = sourceView as? UIBarButtonItem {
popoverController?.barButtonItem = barButtonItem
}
popoverController?.permittedArrowDirections = arrowDirection
present(popover, animated: true, completion: nil)
}
}
You can now call present(popover: from: size: arrowDirection: ) from any view controller that implements UIPopoverPresentationControllerDelegate eg.
class YourViewController : UIViewController {
#IBAction func someButtonPressed(_ sender: UIButton) {
let popover = SomeViewController()
present(popover: popover, from: sender, size: CGSize(width: 280, height: 400), arrowDirection: .right)
}
}
extension YourViewController : UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
add these two methods in your WEBVIEW class. and add
-(void) prepareForSegue: (UIStoryboardSegue * ) segue sender: (id) sender {
// Assuming you've hooked this all up in a Storyboard with a popover presentation style
if ([segue.identifier isEqualToString: #"showPopover"]) {
UINavigationController * destNav = segue.destinationViewController;
pop = destNav.viewControllers.firstObject;
// This is the important part
UIPopoverPresentationController * popPC = destNav.popoverPresentationController;
popPC.delegate = self;
}
}
- (UIModalPresentationStyle) adaptivePresentationStyleForPresentationController: (UIPresentationController * ) controller {
return UIModalPresentationNone;
}
In the UIAdaptivePresentationControllerDelegate you must use this method:
func adaptivePresentationStyle(for: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle
instead of this:
func adaptivePresentationStyle(for: UIPresentationController) -> UIModalPresentationStyle

Display UIAlertController from UIView/NSObject class

I have working iOS application
In order to support iOS8, I am replacing UIAlertView/UIActionSheet with
UIAlertController.
Problem :
For display UIAlertController I need presentViewController
method of UIViewController class.
But UIAlertView is display from classes which are inherited from
UIView or NSObject,
I can not get [self presentViewController...] method for obvious reason.
My Work :
I tried getting rootViewController form current window and display UIAlertController.
[[[UIApplication sharedApplication] keyWindow].rootViewController presentViewController ...]
but have some rotation problems like if my current view controller do not have rotation support
it will rotate if UIAlertController is open.
Question :
Did any one faced same problem and have safe solution ?
if yes please provide me some example or give some guide
I solved an essentially similar problem today. Like Jageen, I ran into a situation where I wanted to present a UIAlertController but from a non-UIViewController class. In my case, I wanted an alert to pop up when the failure block of a HTTP request is run.
This is what I used and unlike our friend here, it worked quite perfectly for me.
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(errorAlert, animated: true, completion: nil)
The better solution for UIView classes is below
ObjectiveC
UIViewController *currentTopVC = [self currentTopViewController];
currentTopVC.presentViewController.........
- (UIViewController *)currentTopViewController
{
UIViewController *topVC = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
while (topVC.presentedViewController)
{
topVC = topVC.presentedViewController;
}
return topVC;
}
Swift
var topVC = UIApplication.sharedApplication().keyWindow?.rootViewController
while((topVC!.presentedViewController) != nil){
topVC = topVC!.presentedViewController
}
topVC?.presentViewController........
My solution is below:
Swift
class alert {
func msg(message: String, title: String = "")
{
let alertView = UIAlertController(title: title, message: message, preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "Done", style: .Default, handler: nil))
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertView, animated: true, completion: nil)
}
}
Here is sample usage:
let Alert = alert()
Alert.msg("My alert (without title)")
Alert.msg("This is my alert", title: "Warning!")
It looks like you are currently (pre-iOS8) triggering an alert view from within your view object. That's pretty bad practice, as in general alerts should be triggered from actions and logic. And that code should live in controllers.
I suggest you refactor your current code to move the logic that triggers the alert to the correct controller, and then you can easily upgrade to iOS 8 by using self as the controller.
If instead you're calling the alert from an outside object, then pass in the controller to the method that calls the alert. Somewhere upstream you must have knowledge of the controller.
For Swift 4
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)
For Swift 5
UIApplication.shared.windows.last?.rootViewController?.present(alert, animated: true)
I had a situation where a subview contains a button to dismiss it. I present an alert to confirm the action. It sends a message to the delegate - which is the view controller containing the subview - to remove the subview
Originally I presented a UIAlertView from a UIView. Refactoring for UIAlertController, since the UIAlertController can't present itself like a UIAlertView can, I came up with the following (in Swift; easily translated to ObjC):
Add a protocol to the subview:
protocol MySubviewDelegate {
// called when user taps subview/delete button
// or, you could call it from a gesture handler, etc.
func displayAlert(alert : UIAlertController)
// called when user confirms delete from the alert controller
func shouldRemoveSubview(sender : AnyObject)
}
Add a delegate for the subview, and add a handler for the button/gesture tap:
class MySubview : UIView {
var subviewDelegate : MySubviewDelegate!
...
func handleTap(sender : AnyObject) {
// set up the alert controller here
var alert = UIAlertController(title: "Confirm Delete",
message: "This action is permanent. Do you wish to continue?",
preferredStyle: UIAlertControllerStyle.Alert)
// Cancel action
// nil handler means "no action if Cancel button selected"
alert.addAction(UIAlertAction(title: "Cancel",
style: UIAlertActionStyle.Cancel,
handler: nil))
// Confirm action
alert.addAction(UIAlertAction(title: "Confirm",
style: UIAlertActionStyle.Default,
handler: { (action : UIAlertAction!) -> Void in
// call delegate method to perform confirmed action, - i.e. remove
self.subviewDelegate.shouldRemoveSubview(self)
}))
// call delegate method to display alert controller
// send alert object to delegate
self.subviewDelegate.displayAlert(alert)
}
}
Set the calling UIViewController as the delegate of the subview, e.g., in its viewDidLoad() method, and include protocol methods:
class viewController : UIViewController, MySubviewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.subviewDelegate = self
...
}
func displayAlert(alert : UIAlertController) {
presentViewController(alert, animated: true, completion: nil)
}
func shouldRemoveSubview(sender : AnyObject) {
// cast as UIView / MySubview subclass
var subview = sender as MySubview
// remove the subview / perform the desired action
subview.removeFromSuperview()
...
}
...
}
This avoids the need to find the topmost view controller, or pass references to view controllers to subviews (other than in an object/delegate relationship).
In Swift 3:
UIApplication.shared.keyWindow?.rootViewController?.present(alertView, animated: true, completion: nil)
For Display UIAlertController in NSObject Class use below Code.
UIAlertController * popup = [UIAlertController
alertControllerWithTitle:nil
message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction* cancel = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * action) {
[popup dismissViewControllerAnimated:YES completion:nil];
}];
[popup addAction:cancel];
UIViewController *rootViewController = [[Helper shareInstance] topViewController];
[rootViewController presentViewController:popup animated:YES completion:nil];
// Put Below Method in Your Global Helper Class.
- (UIViewController *)topViewController {
return [self topViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController *)topViewController:(UIViewController *)rootViewController {
if (rootViewController.presentedViewController == nil) {
return rootViewController;
}
if ([rootViewController.presentedViewController isMemberOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController;
UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
return [self topViewController:lastViewController];
}
UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
return [self topViewController:presentedViewController];
}
In general, alerts should be handled in the view controller. Here's an example of the code required:
Swift 3
private func displayError(message: String) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert)
let okayAction = UIAlertAction(title: "Okay", style: .default, handler: nil)
alertController.addAction(okayAction)
present(alertController, animated: true, completion: nil)
}
I know the question has been already answered... But As I am also looking for the same issue, but none of the above solutions worked for me.
So after doing many try and error finally, I found a very easy and sustainable solution.
func showError(title: String?, error: String?) {
DispatchQueue.main.async(execute: {
let alert = UIAlertController(title: title, message: error, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
CommonMethods.instance.topMostController()?.present(alert, animated: true, completion: nil)
})
}
static let instance = CommonMethods()
fileprivate func topMostController() -> UIViewController? {
var presentedVC = UIApplication.shared.keyWindow?.rootViewController
while let pVC = presentedVC?.presentedViewController {
presentedVC = pVC
}
if presentedVC == nil { }
return presentedVC
}