I am extending the UICollectionView to create an empty View, I couldn't call self.present. Is there a better way to handle empty view? How should i handle self.present in collectionView?
extension UICollectionView {
func setEmptyView() {
let signUpButton = UIButton(type: .custom)
signUpButton.addTarget(self, action: #selector(onSignIn), for: .touchUpInside)
}
#objc func onSignIn() {
let viewController = SignInController()
viewController.modalPresentationStyle = .fullScreen
}
}
You can present from top controller.. using this extension you will get top controller ...
extension UIApplication {
class func getTopMostViewController() -> UIViewController? {
let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
if var topController = keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
} else {
return nil
}
}
}
Then in your collection view extension
extension UICollectionView {
func setEmptyView() {
let signUpButton = UIButton(type: .custom)
signUpButton.addTarget(self, action: #selector(onSignIn), for: .touchUpInside)
}
#objc func onSignIn() {
let viewController = SignInController()
viewController.modalPresentationStyle = .fullScreen
UIApplication.getTopMostViewController()?.present(viewController, animated: true, completion: nil)
}
}
Related
iOS 14.2, when I tried to present a NavigationController controller programmatically with the code snippet below.
#objc private func handleClick() {
let viewController = MyViewController()
self.present(viewController, animated: true, completion: nil)
}
The bar title in the new controller won't get rendered. Am I missing anything?
class MyViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "TEST" // NOT WORK
self.navigationItem.title = "Title" // NOT WORK
}
}
Also tried the code snippet below to nest a regular View Controller into an UINavigableController but the title is still not rendered.
#objc private func handleHelpClick() {
let innerVC = MyInnerViewController()
innerVC.title = "TEST"
let viewController = UINavigationController(rootViewController: innerVC)
self.present(viewController, animated: true, completion: nil)
}
The documentation says:
A navigation controller builds the contents of the navigation bar dynamically using the navigation item objects (instances of the UINavigationItem class) associated with the view controllers on the navigation stack.
https://developer.apple.com/documentation/uikit/uinavigationcontroller
So from my understanding you have to set the title for your UIViewController itself instead for the UINavigationController.
Example:
class MyViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
class ViewController: UIViewController {
private lazy var button: UIButton = {
let btn = UIButton()
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setTitle("Display NavVC", for: .normal)
btn.setTitleColor(.blue, for: .normal)
btn.addTarget(self, action: #selector(displayNavVC), for: .touchUpInside)
return btn
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
configureButton()
}
private func configureButton() {
view.addSubview(button)
NSLayoutConstraint.activate([
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
#objc
private func displayNavVC() {
let vc = UIViewController()
vc.title = "abc"
let navigationVC = MyViewController(rootViewController: vc)
self.present(navigationVC, animated: true, completion: nil)
}
}
Results in:
#finebel's answer in Playground
import UIKit
import PlaygroundSupport
class MyViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
class ViewController: UIViewController {
private lazy var button: UIButton = {
let btn = UIButton()
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setTitle("Display NavVC", for: .normal)
btn.setTitleColor(.blue, for: .normal)
btn.addTarget(self, action: #selector(displayNavVC), for: .touchDown)
return btn
}()
override func viewDidLoad() {
super.viewDidLoad()
configureButton()
}
private func configureButton() {
view.addSubview(button)
NSLayoutConstraint.activate([
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
#objc
private func displayNavVC() {
let vc = UIViewController()
vc.title = "abc"
let navigationVC = MyViewController(rootViewController: vc)
self.present(navigationVC, animated: true, completion: nil)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = ViewController()
I try to present VCs but buttons in popover menu, but I have hierarchy warnings like this:
Warning: Attempt to present "UIViewController: 0x14def7500" on "MyProject.MainViewController: 0x14f976400" whose view is not in the window hierarchy!
I have MainViewController and PopupMenu VCs classes:
Swift 4.0
class MainViewController: UIViewController, UIPopoverPresentationControllerDelegate {
//... here is my VC code
// showing Popup Menu VC
#IBAction func showPopupMenu(sender: UIButton) {
menuVC = PopupMenu()
menuVC?.modalPresentationStyle = .popover
menuVC?.preferredContentSize = CGSize(width: 150, height: 250)
if let pvc = menuVC?.popoverPresentationController {
pvc.permittedArrowDirections = .up
pvc.delegate = self
pvc.sourceView = sender
pvc.sourceRect = sender.bounds
}
self.present(menuVC!, animated: true, completion: nil)
}
// showing VC from popupMenu VC
#IBAction func showVCFromPopup(from target: PopupMenu, vc: UIViewController) {
target.dismiss(animated: false, completion: nil) // dismiss popup
if target.isBeingDismissed { // check is popup dismissed
vc.modalPresentationStyle = .overCurrentContext
self.present(vc, animated: true, completion: nil)
}
}
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
}// end of class
class PopupMenu: UIViewController {
var button = UIButton()
// here is init's
override func viewDidLoad() {
//... some other code
button.addTarget(self, action: #selector(vcOpen(sender:)), for: .touchUpInside)
}
#IBAction func vcOpen(sender: UIButton) {
if sender == button {
let vc = UIViewController()
if parent != nil { print("PARENT")} // Never will work, no ideas why, so MainVC isn't a parent of PopupMenu
if let mainVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MainViewController") as? MainViewController {
print("# ACTION: Opening VC")
mainVC.showVCFromPopup(target: self, as: vc!) // opening VC
}
}
}
}
But I have warning.
Maybe anyone will find mistakes in my code or have any ideas how to do this.
Thanks for all answers!
I edited you code to pass a reference of the mainVC to the PopupMenu:
class MainViewController: UIViewController, UIPopoverPresentationControllerDelegate {
// showing Popup Menu VC
#IBAction func showPopupMenu(sender: UIButton) {
menuVC = PopupMenu()
menuVC?.modalPresentationStyle = .popover
menuVC?.preferredContentSize = CGSize(width: 150, height: 250)
menuVC?.MainVC = self <--- here
if let pvc = menuVC?.popoverPresentationController {
pvc.permittedArrowDirections = .up
pvc.delegate = self
pvc.sourceView = sender
pvc.sourceRect = sender.bounds
}
self.present(menuVC!, animated: true, completion: nil)
}
}
class PopupMenu: UIViewController {
var mainVC: UIViewController <-- here
#IBAction func vcOpen(sender: UIButton) {
if sender == button {
mainVC.showVCFromPopup(target: self, as: vc!) <-- here
}
}
}
I'm trying to display a ViewController as a popover on an iPhone. I have already been through several answers on SO and the rest of the web but none have worked so far. I wrote a simple app to test this.
ViewController.swift:
import UIKit
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(clicked(_:)))
}
func clicked(_ sender: Any) {
let vc = UIViewController()
vc.view.backgroundColor = UIColor.blue
vc.preferredContentSize = CGSize(width: 200, height: 200)
vc.modalPresentationStyle = .popover
present(vc, animated: true, completion: nil)
let ppc = vc.popoverPresentationController
ppc?.permittedArrowDirections = .any
ppc?.delegate = self
ppc?.barButtonItem = navigationItem.rightBarButtonItem
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
}
The storyboard has an empty ViewController embedded in a NavigationController.
Running this, I expected a popover view controller to show under the "done" button. Instead, the blue view controller is presented full screen.
Is there a way to change this behaviour?
You are connecting delegate after presenting view. How it will return .none from delegate and show as popover. Use this :-
func clicked(_ sender: Any) {
let vc = UIViewController()
vc.view.backgroundColor = UIColor.blue
vc.modalPresentationStyle = .popover
vc.preferredContentSize = CGSize(width: 200, height: 200)
let ppc = vc.popoverPresentationController
ppc?.permittedArrowDirections = .any
ppc?.delegate = self
ppc?.barButtonItem = navigationItem.rightBarButtonItem
ppc?.sourceView = sender
present(vc, animated: true, completion: nil)
}
import UIKit
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(clicked(_:)))
}
func clicked(_ sender: Any) {
let vc = UIViewController()
vc.view.backgroundColor = UIColor.blue
vc.preferredContentSize = CGSize(width: 200, height: 200)
vc.modalPresentationStyle = .popover
let ppc = vc.popoverPresentationController
ppc?.permittedArrowDirections = .any
ppc?.delegate = self
ppc!.sourceView = sender as? UIView
ppc?.barButtonItem = navigationItem.rightBarButtonItem
present(vc, animated: true, completion: nil)
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
The solutions above no longer work in recent iOS versions 12 and above. To get it working again, override -modalPresentationStyle within the viewController to be presented as a popover and return UIModalPresentationPopover.
Additionally provide a popoverPresentationController.delegate, implement adaptivePresentationStyleForPresentationController:traitCollection: and return UIModalPresentationNone.
#interface PopoverViewController : UIViewController
#end
#implementation PopoverViewController
- (UIModalPresentationStyle)modalPresentationStyle
{
return UIModalPresentationPopover;
}
#end
#interface ViewController ()<UIPopoverPresentationControllerDelegate>
#end
#implementation ViewController
- (IBAction)openPopover:(id)sender
{
UIViewController* testVC = [[PopoverViewController alloc] init];
testVC.view.backgroundColor = UIColor.yellowColor;
UIPopoverPresentationController* popPresenter = [testVC popoverPresentationController];
popPresenter.permittedArrowDirections = UIPopoverArrowDirectionUp;
popPresenter.delegate = self;
popPresenter.sourceView = sender;
popPresenter.sourceRect = [sender bounds];
[self presentViewController:testVC animated:YES completion:^{}];
}
#pragma mark protocol (UIPopoverPresentationControllerDelegate)
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection
{
return UIModalPresentationNone;
}
#end
Add:
vc.popoverPresentationController?.delegate = self
just before the line:
present(vc, animated: true, completion: nil)
I'm eliminating the storyboard from my app completely. How do I present the VC that is linked to the second tab of the TabBarController.
Setup: mainVC --- myTabBar -- tab1 - navCntrl - VC1
tab2 - navCntrl - VC2
When using a segues I used the following code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == myTabBar) {
let tabVC = segue.destination as? UITabBarController {
tabVC.selectedIndex = myTabBarIndex ==> 1 to reach VC2
}
// other other stuff
}
To eliminating the segues I rewrote the above but although I set the selectedIndex VC2 is not presented. Any suggestions?
func vc2Btn() {
let tabVC = MyTabBar()
tabVC.selectedIndex = 1 // ==>> Index set but can not reach VC2
present(tabVC, animated: true, completion: nil)
}
The full code of my test system:
class MyTabBar: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create Tab 1
let navCtrlTab1 = UINavigationController(rootViewController: VC1())
let tabOne = navCtrlTab1
let tabOneBarItem = UITabBarItem(title: "", image: StyleKit.imageOfIconTabRecent, selectedImage: StyleKit.imageOfIconTabRecentRev)
tabOne.tabBarItem = tabOneBarItem
// Create Tab 2
let navCtrlTab2 = UINavigationController(rootViewController: VC2())
let tabTwo = navCtrlTab2
let tabTwoBarItem = UITabBarItem(title: "", image: StyleKit.imageOfIconTabNote, selectedImage: StyleKit.imageOfIconTabNoteRev)
tabTwo.tabBarItem = tabTwoBarItem
self.viewControllers = [tabOne, tabTwo]
}
}
class mainVC: UIViewController {
let btn0: UIButton = {
let button = UIButton()
button.setBackgroundImage(StyleKit.imageOfBtnBlue(btnText: "VC1"), for: UIControlState.normal)
button.addTarget(self, action:#selector(vc1Btn), for: .touchUpInside)
return button
}()
let btn1: UIButton = {
let button = UIButton()
button.setBackgroundImage(StyleKit.imageOfBtnBlue(btnText: "VC2"), for: UIControlState.normal)
button.addTarget(self, action:#selector(vc2Btn), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(btn0)
self.view.addSubview(btn1)
addConstraintsWithFormat("H:|-100-[v0]", views: btn0)
addConstraintsWithFormat("H:|-100-[v0]", views: btn1)
addConstraintsWithFormat("V:|-300-[v0]-20-[v1]", views: btn0, btn1)
}
func addConstraintsWithFormat(_ format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
func vc1Btn() {
let tabVC = MyTabBar()
tabVC.selectedIndex = 0 // ==>> this is working
present(tabVC, animated: true, completion: nil)
}
func vc2Btn() {
let tabVC = MyTabBar()
tabVC.selectedIndex = 1 // ==>> Index set but can not reach VC2
present(tabVC, animated: true, completion: nil)
}
}
class VC1: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "VC1"
print ("VC1")
}
}
class VC2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "VC2"
print ("VC2")
}
}
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = mainVC()
return true
}
}
When I try to display a popover view controller programmatically it won't work and I don't know why. I've copied from multiple sources on the web and nothing seems to work, I get the same error in the console every time showing Warning: Attempt to present <AddFriendsPopoverViewController> on <MainPageViewController> whose view is not in the window hierarchy! I am lost and can't seem to figure out what the problem is, thanks in advance!
Here is my swift code in my viewDidLoad() function:
let addFriendsPopoverViewController = AddFriendsPopoverViewController()
override func viewDidLoad() {
super.viewDidLoad()
if (PFUser.currentUser()?["numberOfFriends"])! as! NSObject == 0 {
print(PFUser.currentUser()?["numberOfFriends"])
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("AddFriendsPopoverViewController") as! UIViewController
vc.modalPresentationStyle = UIModalPresentationStyle.Popover
vc.preferredContentSize = CGSizeMake(50, 50)
let popoverMenuViewController = vc.popoverPresentationController
popoverMenuViewController!.permittedArrowDirections = .Any
popoverMenuViewController!.delegate = self
popoverMenuViewController!.sourceView = self.view
popoverMenuViewController!.sourceRect = CGRectMake(
100,
100,
0,
0)
self.presentViewController(vc, animated: true, completion: nil)
}
}
EDIT
I figured out that for a popover to work with iPhone the following code is required.
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController!) -> UIModalPresentationStyle {
// Return no adaptive presentation style, use default presentation behaviour
return .None
}
Your view is not in the view hierarchy until it has been presented and not during viewDidLoad:
Move your code to viewDidAppear:
if (PFUser.currentUser()?["numberOfFriends"])! as! NSObject == 0 {
addFriendsPopoverViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
addFriendsPopoverViewController.preferredContentSize = CGSizeMake(200, 200)
let popoverMenuViewController = addFriendsPopoverViewController.popoverPresentationController
popoverMenuViewController!.permittedArrowDirections = .Any
popoverMenuViewController!.delegate = self
popoverMenuViewController!.sourceView = self.view
popoverMenuViewController!.sourceRect = CGRect(
x: 50,
y: 50,
width: 1,
height: 1)
presentViewController(
addFriendsPopoverViewController,
animated: true,
completion: nil)
}
Your code is working right but u can not write that presentViewController code in ViewDidLoad method because viewdidLoad call till that time controller itself it not presented thats why it's not allow to presentViewController .
Write that same code in..
override func viewDidAppear(animated: Bool)
{
var controller = UIViewController()
controller.view.backgroundColor = UIColor .greenColor()
presentViewController(controller, animated: true, completion: nil)
}
I have made it simple for multiple use and its ready to use and go. just copy and paste this extension.
extension UIViewController: UIPopoverPresentationControllerDelegate{
#discardableResult func presentPopOver(_ vcIdentifier: String, _ isAnimate: Bool = true,sender:UIView,contentSize:CGSize = .init(width: 100, height: 100)) -> (UIViewController){
let popoverContentController = storyboard?.instantiateViewController(withIdentifier: vcIdentifier)
popoverContentController?.modalPresentationStyle = .popover
if let popoverPresentationController = popoverContentController?.popoverPresentationController {
popoverPresentationController.permittedArrowDirections = .up
popoverPresentationController.sourceView = sender
popoverPresentationController.sourceRect = sender.bounds
popoverContentController?.preferredContentSize = contentSize
popoverPresentationController.delegate = self
if let popoverController = popoverContentController {
present(popoverController, animated: isAnimate, completion: nil)
}
}
return popoverContentController ?? UIViewController()
}
public func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
public func popoverPresentationControllerDidDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) {
}
public func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool{
return true
}}
**how to use for presenting pop over on button click:-**
#IBAction func sortClicked(_ sender: UIButton) {
presentPopOver("PopOverVC", sender: sender)
}
***presenting pop over and to get and pass data:-***
#IBAction func sortClicked(_ sender: UIButton) {
let vc = presentPopOver("PopOverVC", sender: sender) as? PopOverVC
vc?.arrayNames = ["name1","name2"]
vc?.callBack = {name in
print(name)
vc?.dismiss(animated: true)
}
}