I'm trying to let the user create a new contact. While I've got the screen to prompt the user to put in all his details there is no navigation bar at the top(Like there is in the default Apple Contacts app). There is no way to exit the scene. I'm using the ContactUI framework in swift 2.0 and Xcode 7.3. Here's the code:
// create a new contact
let createNewActionHandler = {(action: UIAlertAction) -> Void in
let newContact = CNMutableContact()
let contactPicker = CNContactViewController(forNewContact: newContact)
contactPicker.delegate = self
contactPicker.navigationController?.setToolbarHidden(false, animated: false)
self.presentViewController(contactPicker, animated: true, completion: nil)
}
Here's what I'm trying to get:
Apple Default
Here's what I have:
What I have
I'm launching the new contact view controller from an action sheet in a tab view controller. I tried embedding the tab in a Navigation view controller but to no effect. I even tried setting the setToolbarHidden property of the navController but it didn't help.
Thanks for any help. I saw the issue raised in other forums but they didn't help.
You have to embed contactViewController to UINavigationController
and Implement Delegate Methods.
let createNewActionHandler = {(action: UIAlertAction) -> Void in
let newContact = CNMutableContact()
let contactPicker = CNContactViewController(forNewContact: newContact)
contactPicker.delegate = self
let navigation = UINavigationController(rootViewController: contactPicker)
self.presentViewController(navigation, animated: true, completion: nil)
}
//MARK: - Delegate
func contactViewController(viewController: CNContactViewController, didCompleteWithContact contact: CNContact?) {
viewController.dismissViewControllerAnimated(true, completion: nil)
}
func contactViewController(viewController: CNContactViewController, shouldPerformDefaultActionForContactProperty property: CNContactProperty) -> Bool {
return true
}
The view controllers must be embedded in UINavigationController and you should push or show view controller:
navigationController?.pushViewController(contactPicker, animated: true)
instead of presenting view controller
Since you are presenting contactPicker viewcontroller on top of current active controller, you will not have access to navigationbar as the view is presented fully,if you want to have button's as in Apple contact app you need to embed your presenting viewcontroller inside UINavigationController, and add left and right bar button items.
Refer the following apple sample which demonstrates the same.
https://developer.apple.com/library/ios/samplecode/iPhoneCoreDataRecipes/Listings/Classes_RecipeAddViewController_m.html
I tried all methods but none worked, So I made a custom method
extension UIBarButtonItem {
private struct AssociatedObject {
static var key = "action_closure_key"
}
var actionClosure: (()->Void)? {
get {
return objc_getAssociatedObject(self, &AssociatedObject.key) as? ()->Void
}
set {
objc_setAssociatedObject(self, &AssociatedObject.key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
target = self
action = #selector(didTapButton(sender:))
}
}
#objc func didTapButton(sender: Any) {
actionClosure?()
}
}
extension UIViewController {
func addDissmissButton(){
let cancelButton = UIBarButtonItem.init(title: "Dismiss", style: .plain, target: self, action: nil)
cancelButton.actionClosure = {
self.dismiss(animated: true)
}
self.navigationItem.leftBarButtonItem = cancelButton
}
}
Related
I have this kind of flow page :
Setting Page --> Enter Password --> Go to Page X
So in setting page, in order to go to page X, I have to enter a password using pop up view.
When I'm in page X and i want to go back to previous page, I go to enter password pop up view, instead of the setting page.
How do I skip that page?
My pop up view code is something like this :
let btn = storyboard?.instantiateViewController(withIdentifier: "PopUpView") as! PopUpView
addChild(btn)
btn.view.frame = view.frame
view.addSubview(btn.view)
btn.didMove(toParent: self)
I'm fairly new here, any help will be appreciated.
Thankyou.
use the settings view controller's present function to open the popup.
//In your settings view
func openPopup() {
let btn = storyboard?.instantiateViewController(withIdentifier: "PopUpView") as! PopUpView
self.present(btn, animated: true, completion: nil)
}
When the user clicks ok, call dismiss on your popup view and use the closure to initiate the page opening to X
//In your 'PopUpView' On the OK button action of your popup
func didTouchOk() {
self.dismiss(animated: true) { [weak self] in
guard let self = self else { return }
//Put your open page X code here
let XView = storyboard?.instantiateViewController(withIdentifier: "XViewController") as! XView
self.present(XView, animated: true, completion: nil)
}
}
if you are using navigationController:
Declare a protocol in your PopUpView
protocol popupDelegate: class {
func userDidTouchOkInPopup()
}
create a weak var in your PopUpView
weak var delegate: popupDelegate? = nil
In your settings viewcontroller:
Assign delegate to popup before pushing
func openPopup() {
let btn = storyboard?.instantiateViewController(withIdentifier:
"PopUpView") as! PopUpView
btn.delegate = self
self.navigationController?.pushViewController(btn, animated: true)
}
Implement an extension for this protocol
extension SettingsViewController: popupDelegate {
func userDidTouchOkInPopup() {
self.navigationController?.popViewController(animated: true)
let XView = storyboard?.instantiateViewController(withIdentifier:
"XViewController") as! XView
self.navigationController?.pushViewController(XView, animated: true)
}
}
Modify the Ok action in your PopUpView
//In your 'PopUpView' On the OK button action of your popup
func didTouchOk() {
self.delegate?.userDidTouchOkInPopup()
}
If you are in navigationController hierarchy, use navigationController.popToViewController to go to the specific ViewController.
In ViewDidLoad, hide the present backButton and create a new One and associate action with it.
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(backButtonFunction))
self.navigationItem.leftBarButtonItem = newBackButton
}
Inside backButtonFunction, you can put the code
#objc func backButtonFunction(){
if let navController = self.navigationController {
for controller in navController.viewControllers {
if controller is SettingsViewController { // Change name of ViewController accordingly
navController.popToViewController(controller, animated:true)
break
}
}
}
}
I have a tab bar view. In that, one of three tabs is a contacts app. In that contacts app, there are three pages, one to display all the contacts, second to display one particular contact selected in the first VC, and third is to add a new contact. (Pretty much similar to the built-in iOS app) How can I create a fluent flow between these three pages. (Do not forget they should be inside one tabbar)
I've added all the required buttons.
First Page - Display all contacts
Second Page - Show one particular contact
Third Page - Add new contact
I've embedded 3 navigation view controller (one to each of the 3 pages)
First page has 'add' button that should lead to third page.
Second page has two buttons, one to go back to first and the other to go to third.
Third has two buttons, one each to go the each of the other two pages.
I've created these buttons programmatically inside the navigation bar using this function -
override func viewDidAppear(_ animated: Bool) {
navigationItem.title = "Add New Contact"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.plain, target: self, action: #selector(theTransition))
}
#objc func theTransition() {
let second = self.storyboard?.instantiateViewController(withIdentifier: "OneNavID") as! OneNavigationViewController
self.present(second, animated: true, completion: nil)
}
(The above code is an example of going from page 3 to 1)
These buttons are working properly, but they show a new page which is out of the tab bar controller.
I want them to stay inside the tab view. Kindly help! Do ask if you have any doubt in the question. I've tried best to explain it in simple and short words.
I've looked on the internet a lot, but I couldn't find any instance where each of three view controllers have one navigation controller each (which I assume is needed since each of them has incoming and outgoing links to/from other pages).
Second Page -
override func viewDidAppear(_ animated: Bool) {
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Edit", style: UIBarButtonItem.Style.plain, target: self, action: #selector(theTransition))
}
#objc func theTransition() {
let second = self.storyboard?.instantiateViewController(withIdentifier: "ThreeNavID") as! ThreeNavigationViewController
self.present(second, animated: true, completion: nil)
}
Third Page -
override func viewDidAppear(_ animated: Bool) {
navigationItem.title = "Add New Contact"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: UIBarButtonItem.Style.plain, target: self, action: #selector(theTransition))
}
#objc func theTransition() {
let second = self.storyboard?.instantiateViewController(withIdentifier: "NavID") as! NavigationViewController
self.present(second, animated: true, completion: nil)
}
Don't use 3 navigation controllers. Use one navigation controller like this
UITabBarController_____ Another View Controller1
|
|____ Another View Controller2
|
|____ Contact - UINavigationController -- FirstPage
UINavigationController -- FirstPage --> SecondPage --> ThirdPage
Embed the first Page in the navigation controller and push second and third pages. Don't create a new instance for the first page. Use popViewController to go to the previous view controller.
You can pass data to the next view controller with pushViewController and use custom delegate or closure to send data to the previous view controller. Or create a variable in custom navigation controller class like this
class CustomNavigationController: UINavigationController {
var name: String?
}
Then read and write data from other view controllers like this
class FirstPage: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print((self.navigationController as? CustomNavigationController)?.name)
(self.navigationController as? CustomNavigationController)?.name = "FirstPage"
}
#objc func goToSecondPage() {
if let secondPage = self.storyboard?.instantiateViewController(withIdentifier: "SecondPage") as? SecondPage {
self.navigationController?.pushViewController(secondPage, animated: true)
}
}
#objc func goToThirdPage() {
if let thirdPage = self.storyboard?.instantiateViewController(withIdentifier: "ThirdPage") as? ThirdPage {
self.navigationController?.pushViewController(thirdPage, animated: true)
}
}
}
class SecondPage: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print((self.navigationController as? CustomNavigationController)?.name)
(self.navigationController as? CustomNavigationController)?.name = "SecondPage"
}
#objc func goToThirdPage() {
if let thirdPage = self.storyboard?.instantiateViewController(withIdentifier: "ThirdPage") as? ThirdPage {
self.navigationController?.pushViewController(thirdPage, animated: true)
}
}
#objc func goToFirstPage() {
self.navigationController?.popViewController(animated: true)
}
}
class ThirdPage: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print((self.navigationController as? CustomNavigationController)?.name)
(self.navigationController as? CustomNavigationController)?.name = "ThirdPage"
}
#objc func goToFirstPage() {
self.navigationController?.popToRootViewController(animated: true)
}
#objc func goToSecondPage() {
self.navigationController?.popViewController(animated: true)
}
}
My app starts with a login screen that segues to CreateRequestTableViewController, and everything is embedded in a navigation controller, so the back button for the CreateRequest vc goes back to the login screen. I want to ask the user if they're sure they before they're logged out and the navcon pops the vc to show the Login screen again.
I've gotten it to work with the code below, except that after I log back in and move back to the CreateRequest VC (creating a new instance) I get a fatal error:
'NSInternalInconsistencyException', reason: 'Cannot manually set the delegate on a UINavigationBar managed by a controller.'
This puts me in just a little bit over my head. I've tried adding the deinit method that's included in the code below, with no luck.
It's especially strange that it doesn't crash the first time I assign the delegate (or when I set it to nil either), as the text of the error would suggest.
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.delegate = self
}
deinit {
navigationController?.navigationBar.delegate = nil
}
func confirmLogout() {
let alert = UIAlertController(title: "Log Out", message: "Are you sure you want to log out?", preferredStyle: .alert)
let yesButton = UIAlertAction(title: "Log out", style: .destructive) { (_) in
if let loginVC = self.navigationController?.viewControllers.first as? SignInTableViewController {
self.navigationController?.popViewController(animated: true)
loginVC.logOutAll()
}
}
let noButton = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alert.addAction(yesButton)
alert.addAction(noButton)
present(alert, animated: true, completion: nil)
}
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if navigationController?.viewControllers.last is CreateRequestTableViewController {
confirmLogout()
return false
}
navigationController?.popViewController(animated: true)
return true
}
I've solved this problem by deriving custom navigation controller that sets up it's own specialised navigation bar delegate.
This delegate (Forwarder):
Is set up only once and before the navigation controller takes
control over the navigation bar (in the navigation controller's
initialiser).
Receives UINavigationBarDelegate messages and tries to
call your navigation bar delegate first, then eventually the original navigation
bar delegate (the UINavigationController).
The custom navigation controller adds a new "navigationBarDelegate" property that you can use to setup your delegate. You should do that in viewDidAppear:animated: and viewWillDisappear:animated: methods.
Here is the code (Swift 4):
class NavigationController : UINavigationController
{
fileprivate var originaBarDelegate:UINavigationBarDelegate?
private var forwarder:Forwarder? = nil
var navigationBarDelegate:UINavigationBarDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if navigationBar.delegate != nil {
forwarder = Forwarder(self)
}
}
}
fileprivate class Forwarder : NSObject, UINavigationBarDelegate {
weak var controller:NavigationController?
init(_ controller: NavigationController) {
self.controller = controller
super.init()
controller.originaBarDelegate = controller.navigationBar.delegate
controller.navigationBar.delegate = self
}
let shouldPopSel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:))
let didPopSel = #selector(UINavigationBarDelegate.navigationBar(_:didPop:))
let shouldPushSel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPush:))
let didPushSel = #selector(UINavigationBarDelegate.navigationBar(_:didPush:))
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if let delegate = controller?.navigationBarDelegate, delegate.responds(to: shouldPopSel) {
if !delegate.navigationBar!(navigationBar, shouldPop: item) {
return false
}
}
if let delegate = controller?.originaBarDelegate, delegate.responds(to: shouldPopSel) {
return delegate.navigationBar!(navigationBar, shouldPop: item)
}
return true
}
func navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem) {
if let delegate = controller?.navigationBarDelegate, delegate.responds(to: didPopSel) {
delegate.navigationBar!(navigationBar, didPop: item)
}
if let delegate = controller?.originaBarDelegate, delegate.responds(to: didPopSel) {
return delegate.navigationBar!(navigationBar, didPop: item)
}
}
func navigationBar(_ navigationBar: UINavigationBar, shouldPush item: UINavigationItem) -> Bool {
if let delegate = controller?.navigationBarDelegate, delegate.responds(to: shouldPushSel) {
if !delegate.navigationBar!(navigationBar, shouldPush: item) {
return false
}
}
if let delegate = controller?.originaBarDelegate, delegate.responds(to: shouldPushSel) {
return delegate.navigationBar!(navigationBar, shouldPush: item)
}
return true
}
func navigationBar(_ navigationBar: UINavigationBar, didPush item: UINavigationItem) {
if let delegate = controller?.navigationBarDelegate, delegate.responds(to: didPushSel) {
delegate.navigationBar!(navigationBar, didPush: item)
}
if let delegate = controller?.originaBarDelegate, delegate.responds(to: didPushSel) {
return delegate.navigationBar!(navigationBar, didPush: item)
}
}
}
Here is the usage:
Derive your view controller from UINavigationBarDelegate
Change the class name of the navigation controller in your storyboard from UINavigationController to NavigationController.
Put the following code into your view controller
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
(navigationController as? NavigationController)?.navigationBarDelegate = self
}
override func viewWillDisappear(_ animated: Bool) {
(navigationController as? NavigationController)?.navigationBarDelegate = nil
super.viewWillDisappear(animated)
}
implement one or more of UINavigationBarDelegate methods in your view controller (this is just an example):
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
let alert = UIAlertController(title: "Do you really want to leave the page?", message: "All changes will be lost", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Stay here", style: .default, handler: nil))
alert.addAction(UIAlertAction(title: "Leave", style: .destructive, handler: { action in
self.navigationController?.popViewController(animated: true)
}))
self.present(alert, animated: true)
return false
}
The error message is clear. You are free to set a navigation controller’s delegate. But you must not set the delegate of its navigation bar; you will break the navigation controller if you do that, because it is the delegate of the bar.
Put this method into the class of root view not the view that you need to pop. I had this same problem and I found the error when I implemented this method in the view that I was going to pop.
I want to display iAd in a popover. I use a shared instance class to call the displayAd method. here is my shared instance class :
class Share : NSObject ,UIPopoverPresentationControllerDelegate {
static let sharedInstance = Share()
func displayAd(sender:UIViewController) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("PopOverVC") as UIViewController
vc.preferredContentSize = CGSize(width: 310, height: 250)
let navC = UINavigationController(rootViewController: vc)
navC.modalPresentationStyle = UIModalPresentationStyle.Popover
let popOver = navC.popoverPresentationController
popOver?.delegate = self
popOver?.sourceView = sender.view
popOver?.sourceRect = CGRectMake(CGRectGetMidX(sender.view.bounds), CGRectGetMidY(sender.view.bounds),0,0)
popOver?.permittedArrowDirections = UIPopoverArrowDirection(rawValue:0)
navC.navigationBarHidden = true
sender.presentViewController(navC, animated: true) {}
}
}
I use the displayAd function to display a popover (which contains PopOverVC which is an iAd)
and then this is the PopOverVC class:
class PopOverVC: UIViewController,ADBannerViewDelegate {
var ad = ADBannerView()
#IBOutlet var Banner: ADBannerView!
#IBAction func CloseBtn(sender: UIButton) {
self.dismissViewControllerAnimated(true, completion: nil)
}
override func viewDidLoad() {
Banner = ad
}
func bannerViewDidLoadAd(banner: ADBannerView!) {
sharedAd.hidden = false
}
func bannerView(banner: ADBannerView!, didFailToReceiveAdWithError error: NSError!) {
sharedAd.hidden = true
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Now, what I want to do is call displayAd method from some other View Controller and then check if iAd is available and then show it.
Right now my code shows the PopOver and then check for availability of iAd and if not available, it closes.
I don't want to show PopOver unless the iAd is available.
Is there anyway to achieve this?
thanks
You can simply ask the ADBannerView with the bannerLoaded property:
Banner views automatically download new advertisements in the background. This property returns true if an advertisement is loaded; false otherwise.
However, as Daniel pointed out above, you have six weeks to ship a replacement for your iAd code, so I very strongly recommend you work on that instead!
I've got a timer showing an alert when finished.
This alert view should be presented in the view controller which the user is currently in.
My feeling is this could be accomplished much more effective than the following:
The way I'm doing this now is give an observer for a notification to each of my 5 view controllers as well as a method to create and present that alert.
Is there a way to only set up the alert once and then present it in the view controller that is currently active?
Here's my code:
// I've got the following in each of my view controllers.
// In viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(SonglistViewController.presentSleepTimerFinishedAlert(_:)), name: "presentSleepTimerFinishedAlert", object: nil)
}
func presentTimerFinishedAlert(notification: NSNotification) {
let alertController = UIAlertController(title: "Timer finished", message: nil, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
presentViewController(alertController, animated: true, completion: nil)
}
Thanks a lot for any ideas!
extension UIApplication {
/// The top most view controller
static var topMostViewController: UIViewController? {
return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
}
}
extension UIViewController {
/// The visible view controller from a given view controller
var visibleViewController: UIViewController? {
if let navigationController = self as? UINavigationController {
return navigationController.topViewController?.visibleViewController
} else if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.visibleViewController
} else if let presentedViewController = presentedViewController {
return presentedViewController.visibleViewController
} else {
return self
}
}
}
With this you can easily present your alert like so
UIApplication.topMostViewController?.present(alert, animated: true, completion: nil)
One thing to note is that if there's a UIAlertController currently being displayed, UIApplication.topMostViewController will return a UIAlertController. Presenting on top of a UIAlertController has weird behavior and should be avoided. As such, you should either manually check that !(UIApplication.topMostViewController is UIAlertController) before presenting, or add an else if case to return nil if self is UIAlertController
extension UIViewController {
/// The visible view controller from a given view controller
var visibleViewController: UIViewController? {
if let navigationController = self as? UINavigationController {
return navigationController.topViewController?.visibleViewController
} else if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.visibleViewController
} else if let presentedViewController = presentedViewController {
return presentedViewController.visibleViewController
} else if self is UIAlertController {
return nil
} else {
return self
}
}
}
You can find the Top ViewController on the navigation stack and directly present the AlertController from there. You can use the extension method posted here to find the Top ViewController from anywhere in your application:
https://stackoverflow.com/a/30858591/2754727
It really depends on your navigation schema.
First of all you will need current VC. If you've got root view controller as navigation controller and don't show any modals you can get current VC from rootVC. If you've got mixed navigation. i.e. tabbar and then navigation controllers inside, with possible some modals form them you can write an extension on AppDelegate which will search and return current VC.
Now you should pin somewhere this timer class - it may be a singleton or just be pinned somewhere. Than in this timer class, when the timer ends you can look for current VC (using AppDelegate's extension method or referring to your root navigation controller) an present an alert on it.