Implement PopOver in Swift - ios

I want to show a popup menu and without using nib.I dont like using nib since the headache to implement delegate for a simple functionality. I succeeded using modalPresentationStyle as a Popover to show ViewController as a Popover and it works fine with the below code.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var btnShowPopOver: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func showPopUP(sender: AnyObject) {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let popVC = storyboard.instantiateViewControllerWithIdentifier("pop")as! PopViewController
popVC.modalPresentationStyle = UIModalPresentationStyle.Popover
popVC.preferredContentSize = CGSizeMake(320, 240)
popVC.popoverPresentationController!.delegate = self
let popOverController = popVC.popoverPresentationController
popOverController!.sourceView = sender as! UIView // where to stick the bar item in which view
popOverController!.sourceRect = CGRectMake(70,30, 0, 0) //where to stick the bar
popOverController?.permittedArrowDirections = nil
self.presentViewController(popVC, animated: true, completion: nil)
}
}
extension ViewController :UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyleForPresentationController(PC: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
func popoverPresentationControllerDidDismissPopover(popoverPresentationController: UIPopoverPresentationController) {
var controller = popoverPresentationController.presentedViewController as! PopViewController
println(" this is data from pop view controller \(controller.textField.text)")
}
}
Apple doc says
Popover controllers are for use exclusively on iPad devices.
Attempting to create one on other devices results in an exception.
I tested this on real device iphone-6 and its working fine..I am loving this Popover.
Should i use Popover or not as per the apple documentation?Since its working fine in iphone,will my app get rejected for using it later?

Since iOS8 we are now able to create popovers, that will be the same
on iPhone, as on iPad, which would be especially awesome for those who
make universal apps, thus no need to make separate views or code.
Source : UIPopoverController for iphone not working?
Look at this answers :
https://stackoverflow.com/a/14789022/3202193
https://stackoverflow.com/a/30418212/3202193
In the per-release document of iOS 9 also they are saying like :
The UIPopoverController class is used to manage the presentation of content in a popover. You use popovers to present information temporarily. The popover content is layered on top of your existing content and the background is dimmed automatically. The popover remains visible until the user taps outside of the popover window or you explicitly dismiss it. Popover controllers are for use exclusively on iPad devices. Attempting to create one on other devices results in an exception.
EDIT
I think you have used the UIPopoverPresentationController which is Available in iOS 8.0 and later.
The UIPopoverController and UIPopoverPresentationController are two different things provided by Apple.

Related

Popover presentation style on iPhone devices - possible any more?

I'm trying to define a popover view attached to a view like this:
Here's my code:
class MyController: UIViewController, UIPopoverPresentationControllerDelegate {
...
func displaySignOut(_ sender: UIButton) {
let vc = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "signOutPopover")
vc.modalPresentationStyle = .popover
vc.preferredContentSize = CGSize(width: 100, height: 30)
present(vc, animated: true, completion: nil)
let pc = vc.popoverPresentationController!
pc.sourceView = sender
pc.sourceRect = sender.bounds
pc.delegate = self
}
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
Because the popover is so small, I'd like to use this style on all devices. I've followed the usual advice (e.g., here) on overriding adaptivePresentationStyle to return UIModalPresentationStyle.none.
This works fine on iPad devices, but on iPhones, it doesn't. On smaller iPhone devices, it comes up full screen all the time. On larger screens (e.g., iPhone 7 Plus), it comes up wrong, but, weirdly, switches to a popover presentation (in both portrait and landscape) if I rotate the device after the popover appears. (If I dismiss the popover and bring it up again, it's wrong again until I rotate the device.) Furthermore, in landscape it comes up in a strange configuration (not full screen as in portrait):
Unlike with a popover presentation, this does not dismiss if I tap outside the popover view itself.
The Apple documentation says (in part):
In a horizontally compact environment, popovers adapt to the UIModalPresentationOverFullScreen presentation style by default.
The "by default" strongly suggests that there's a way to override this behavior. But (as is consistent with this post), overriding adaptivePresentationStyle in the delegate doesn't seem to be the way to do this any more (although it used to work). So is there a new way to modify the default behavior?
I'm using XCode 8.3.3 and Swift 3.1, targeting iOS 9+.
I have created one custom class with storyboard inside that connect
outlet of button and implemented below code.
import UIKit
class PopOverViewController: UIViewController {
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
button.backgroundColor = UIColor.purple
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//Updating the popover size
override var preferredContentSize: CGSize {
get {
let size = CGSize(width: 80, height: 60)
return size
}
set {
super.preferredContentSize = newValue
}
}
//Setup the ViewController for popover presentation
func updatePopOverViewController(_ button: UIButton?, with delegate: AnyObject?) {
guard let button = button else { return }
modalPresentationStyle = .popover
popoverPresentationController?.permittedArrowDirections = [.any]
popoverPresentationController?.backgroundColor = UIColor.purple
popoverPresentationController?.sourceView = button
popoverPresentationController?.sourceRect = button.bounds
popoverPresentationController?.delegate = delegate
}
}
And then Inside ViewController implemented one function to show
popOver on iphone
func showPopOver(button: UIButton!) {
let viewController = PopOverViewController()
viewController.updatePopOverViewController(button, with: self)
present(viewController, animated: true, completion: nil)
}
Note:- Tested and this should work on Portrait as well Landscape mode
iOS 15 has some new ways to solve this problem.
Take a look at the WWDC21 Session "Customize and Resize Sheets in UIKit" https://developer.apple.com/wwdc21/10063
Pretty simple new interface for popovers and customized sheets. Shows how to do non-modal interaction with pop over and the view behind it.

Custom Splash Screen for iOS App

After some googling I found that messing with the default Xcode launch screen is not the most proper way for make ur splash screen wait for some time and other stuff so I add new view controller (custom class named splash) to my storyboard and then after 2 seconds it's will display my main UINavigationController and it's not working just freeze on the splash screen
Here is my code:
import UIKit
class splash: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NSThread.sleepForTimeInterval(2.0)
let vc = storyboard?.instantiateViewControllerWithIdentifier("mainmenu") as! UINavigationController
self.presentViewController(vc, animated: true, completion: nil)
}
}
i have solve it using perform selector
class splash: UIViewController {
override func viewDidLoad() {
super . viewDidLoad()
performSelector(#selector(splash.showmainmenu), withObject: nil, afterDelay: 2)
}
func showmainmenu(){
performSegueWithIdentifier("mainmenu", sender: self)
}
What are you trying to do - is an ugly hack. Don't.
You should create your custom Splash View Controller with layout that mimics your default splash screen image, perform custom animations if any, and then push/present next view controller (on the main thread), or whatever you want to do according to your app requirements.

An alternative to "Storyboard Reference" for iOS 8 when handling Relationship Segue?

My App has a TabBarViewController containing 4 tabs. One of the tabs is Settings which I want to move to a separate storyboard. If I am only consider iOS 9 and above as my deployment target, then I can just refactor the SettingsTab using Storyboard Reference. However I want to target iOS 8 as well. Since Storyboard Reference doesn't support Relationship Segue, I can't rely on it in this case.
So in the main storyboard which contains the TabBarViewController, I keep a dummy SettingsTabViewController as an empty placeholder. And in the function "viewWillAppear" in its class file, I push the view to the real SettingsTabViewController in the Settings.storyboard. This works fine. But the problem is if I keep tabbing the Settings tab, the empty placeholder view controller will show up for a short time and then goes back to the real Settings view.
I tried to implement this delegate to lock the Settings tab:
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
return viewController != tabBarController.selectedViewController
}
However, the other three tabs were locked too after I implemented this delegate.
Is it possible to just lock the Settings tab without locking other three tabs? And in which view controller exactly should I implement this delegate?
Yes, it's possible. You need to check the index;
with the following code not only you can prevent locking other tabs, but also you still have tap on tab goto root view controller feature.
func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool {
let tappedTabIndex = viewControllers?.indexOf(viewController)
let settingsTabIndex = 3 //change the index
if tappedTabIndex == settingsTabIndex && selectedIndex == settingsTabIndex {
guard let navVC = viewController as? UINavigationController else { return true }
guard navVC.viewControllers.count > 1 else { return true }
let firstRealVC = navVC.viewControllers[1]
navVC.popToViewController(firstRealVC, animated: true)
return false
}
return true
}
.
This answers your question, but still you would have the settingsVC showing up for a moment. To avoid this you simply need to turn off the animation while you're pushing it. so you need to override viewWillAppear in the following way.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if let theVC = storyboard?.instantiateViewControllerWithIdentifier("theVC") {
navigationController?.pushViewController(theVC, animated: false)
}
}
after adding above code you still would see a back button in your real first viewController. You can hide it:
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.hidesBackButton = true
}

Swift - UIPopoverController in iOS 8

I'm trying to add a simple popoverController to my iphone app, and I'm currently struggling with the classic "blank screen" which covers everything when I tap the button.
My code looks like this:
#IBAction func sendTapped(sender: UIBarButtonItem) {
var popView = PopViewController(nibName: "PopView", bundle: nil)
var popController = UIPopoverController(contentViewController: popView)
popController.popoverContentSize = CGSize(width: 3, height: 3)
popController.presentPopoverFromBarButtonItem(sendTappedOutl, permittedArrowDirections: UIPopoverArrowDirection.Up, animated: true)
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController!) -> UIModalPresentationStyle {
// Return no adaptive presentation style, use default presentation behaviour
return .None
}
}
The adaptivePresentationStyleForPresentationController-function was just something I added because I read somewhere that this is what you need to implement to get this function on the iphone. But still: there is still a blank image covering the whole screen, and I do not know how to fix it.
Any suggestions would be appreciated.
The solution I implemented for this is based on an example presented in the 2014 WWDC session View Controller Advancements in iOS 8 (see the slide notes). Note that you do have to implement the adaptivePresentationStyleForPresentationController function as a part of the UIPopoverPresentationControllerDelegate, but that function should be outside of your sendTapped function in your main view controller, and you must specify UIPopoverPresentationControllerDelegate in your class declaration line in that file to make sure that your code modifies that behaviour. I also took the liberty to separate out the logic to present a view controller in a popover into its own function and added a check to make sure the function does not present the request view controller if it is already presented in the current context.
So, your solution could look something like this:
// ViewController must implement UIPopoverPresentationControllerDelegate
class TheViewController: UIViewController, UIPopoverPresentationControllerDelegate {
// ...
// The contents of TheViewController class
// ...
#IBAction func sendTapped(sender: UIBarButtonItem) {
let popView = PopViewController(nibName: "PopView", bundle: nil)
self.presentViewControllerAsPopover(popView, barButtonItem: sender)
}
func presentViewControllerAsPopover(viewController: UIViewController, barButtonItem: UIBarButtonItem) {
if let presentedVC = self.presentedViewController {
if presentedVC.nibName == viewController.nibName {
// The view is already being presented
return
}
}
// Specify presentation style first (makes the popoverPresentationController property available)
viewController.modalPresentationStyle = .Popover
let viewPresentationController = viewController.popoverPresentationController?
if let presentationController = viewPresentationController {
presentationController.delegate = self
presentationController.barButtonItem = barButtonItem
presentationController.permittedArrowDirections = .Up
}
viewController.preferredContentSize = CGSize(width: 30, height: 30)
self.presentViewController(viewController, animated: true, completion: nil)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None
}
}
Real world implementation
I implemented this approach for input validation on a sign up form in an in-progress app that I host on Github. I implemented it as extensions to UIVIewController in UIViewController+Extensions.swift. You can see it in use in the validation functions in AuthViewController.swift. The presentAlertPopover method takes a string and uses it to set the value of a UILabel in a GenericAlertViewController that I have set up (makes it easy to have dynamic text popovers). But the actual popover magic all happens in the presentViewControllerAsPopover method, which takes two parameters: the UIViewController instance to be presented, and a UIView object to use as the anchor from which to present the popover. The arrow direction is hardcoded as UIPopoverArrowDirection.Up, but that wouldn’t be hard to change.

Adaptive show detail segue transformed to modal instead of push on iPhone when master view controller is a UITabBarController

In XCode 6, if you create a new project based on the Master-Detail Application template, you get a universal storyboard that is supposed to be good for all devices.
When selecting a cell in the master view, the detail view is updated via an adaptive "show detail" segue. On an iPhone 4, 5, 6 or 6+ in portrait, this segue will take the form of a push as expected. On an iPad or an iPhone 6+ in landscape, it will cause the detail view to be updated as expected.
Now, if you insert a UITabBarController as the master view controller which has a tab to the original master view controller, the adaptive segue that occurs when selecting a cell in the master view does not behave as expected on iPhones. Instead of getting a push transition, you now get a modal transition. How can I fix that? Seems odd that this is not supported by default.
I found the following post useful: iOS8 TabbarController inside a UISplitviewController Master
But when using the suggested method, I don't get the right behaviour on an iPhone 6 Plus when I rotate to landscape after a push in portrait. The content of the detail view appears in the master view which is not surprising since that's what the suggested solution does.
Thanks!
Re-watching videos from WWDC14 I think I've found a better answer.
Use a custom UISplitViewController (subclass)
Override the showDetailViewController operation
Use the traitCollection to determine the class of the UISplitViewController
If the horizontal class is Compact, get the navigationController to call showViewController
Here is the the code of the custom UISplitViewController :
import UIKit
class CustomSplitViewController: UISplitViewController {
override func showDetailViewController(vc: UIViewController!, sender: AnyObject!) {
if (self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClass.Compact) {
if let tabBarController = self.viewControllers[0] as? UITabBarController {
if let navigationController = tabBarController.selectedViewController as? UINavigationController {
navigationController.showViewController(vc, sender: sender)
return
}
}
}
super.showDetailViewController(vc, sender: sender)
}
}
Do not forget to the set the custom class in the storyboard.
Tested in the simulator of iPhone 6, iPhone 6+ and iPad Air and worked as expected.
Unfortunately, the selected answer didn't work for me. However, I did eventually manage to solve the problem:
Subclass UISplitViewController and set the new class in Interface Builder.
Make the new class conform to UISplitViewControllerDelegate:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.delegate = self
}
Implement these two methods:
func splitViewController(_ splitViewController: UISplitViewController,
collapseSecondary secondaryViewController:UIViewController,
onto primaryViewController:UIViewController) -> Bool {
return true
}
func splitViewController(_ splitViewController: UISplitViewController,
showDetail vc: UIViewController,
sender: Any?) -> Bool {
if splitViewController.isCollapsed {
guard let tabBarController = splitViewController.viewControllers.first as? UITabBarController else { return false }
guard let selectedNavigationViewController = tabBarController.selectedViewController as? UINavigationController else { return false }
// Push view controller
var detailViewController = vc
if let navController = vc as? UINavigationController, let topViewController = navController.topViewController {
detailViewController = topViewController
}
selectedNavigationViewController.pushViewController(detailViewController, animated: true)
return true
}
return false
}
The docs state when the split controller is collapsed, it handles showDetail by calling show on the master view controller, which in your case is a tab controller. You need to forward that on to the child nav controller as follows:
Make a tab controller subclass.
In the storyboard set the tab controller to use the new subclass.
Add this method to the subclass:
- (void)showViewController:(UIViewController *)vc sender:(id)sender{
[self.viewControllers.firstObject showViewController:vc sender:sender];
}
This forwards it on to the nav controller in the first tab.

Resources