I'm having trouble changing the size of my popover presentation. Here is what I have so far
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) // func for popover
{
if segue.identifier == "popoverView"
{
let vc = segue.destinationViewController
let controller = vc.popoverPresentationController
if controller != nil
{
controller?.delegate = self
controller?.sourceView = self.view
controller?.sourceRect = CGRect(x:CGRectGetMidX(self.view.bounds), y: CGRectGetMidY(self.view.bounds),width: 315,height: 230)
controller?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
}
}
}
So far all this does is center the popover and remove the arrow, which is good. but it doesn't resize the container. any help would be greatly appreciated. thank you.
when I use preferredContentSize I get the error "Cannot assign to property: 'preferredContentSize' is immutable"
Set the preferred content size on the view controller being presented not the popoverPresentationController
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) // func for popover
{
if segue.identifier == "popoverView"
{
let vc = segue.destinationViewController
vc.preferredContentSize = CGSize(width: 200, height: 300)
let controller = vc.popoverPresentationController
controller?.delegate = self
//you could set the following in your storyboard
controller?.sourceView = self.view
controller?.sourceRect = CGRect(x:CGRectGetMidX(self.view.bounds), y: CGRectGetMidY(self.view.bounds),width: 315,height: 230)
controller?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
}
}
I fixed it via storyboard :
Click on your controller
Click on Attribute inspector
ViewController>
Check Use Preferred Explicit size and input values.
Using Auto Layout
It may be worth mentioning that you can use layout constraints instead of setting preferredContentSize to specific values. To do so,
Add this to your view controller:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.preferredContentSize = self.view.systemLayoutSizeFitting(
UIView.layoutFittingCompressedSize
)
}
Ensure that you have constraints from your popover view to the controller's root view. These can be low priority, space >= 0 constraints.
Above answers are correct which states about using the preferredContentSize, but the most important thing is to implement the protocol UIPopoverPresentationControllerDelegate and implement the below method otherwise it will not change the content size as expected.
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
Similar to Xeieshan's answer above, I set this via the storyboard.
Except that "Presentation" also needed to be to "Form Sheet".
I'm not using storyboards. I just present a UINavigationController in the popover:
self.present(popoverNavigationController!, animated: true) {}
The way to resize the popover size when a new view controller is pushed, it is just change the preferredContentSize before pushing it. For example:
let newViewController = NewViewController()
popoverNavigationController!.preferredContentSize = CGSize(width: 348, height: 400)
popoverNavigationController!.pushViewController(newViewController, animated: true)
The problem is when we try to resize the popover when we pop a view controller.
If you use viewWillDisappear of the current view controller to change the preferredContentSize of the popover, the popover will resize but after the view controller is popped. That means that the animation has a delay.
You have to change the preferredContentSize before executing popViewController. That's mean you have to create a custom back button in the navigation bar like it is explained here. This is the implementation updated for Swift 4:
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(CurrentViewController.backButtonTapped(sender:)))
self.navigationItem.leftBarButtonItem = newBackButton
And run the next code when the new Back button is pressed:
#objc func backButtonTapped(sender: UIBarButtonItem) {
self.navigationController?.preferredContentSize = CGSize(width: 348, height: 200)
self.navigationController?.popViewController(animated: true)
}
Basically, the preferredContentSize has to be changed before pushing and popping the view controller.
Related
I am trying to lay out a popover view when a button on current view is pressed. This works fine with following code but problem is that the popover view does not take the size from .preferredContentSize.
IngredientVC.preferredContentSize = CGSize(width: 200, height: 300)
Popover view has height and width of current view. How do I make Popover view smaller size please?
#IBAction func actionIngredients(_ sender: Any)
{
// Load and configure your view controller.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let IngredientVC = storyboard.instantiateViewController(
withIdentifier: "testViewIdentifier")
//let IngredientVC:testViewController = testViewController()
// Use the popover presentation style for your view controller.
IngredientVC.modalPresentationStyle = .popover
IngredientVC.preferredContentSize = CGSize(width: 200, height: 300)
// Specify the anchor point for the popover.
//IngredientVC.popoverPresentationController?.barButtonItem =
//optionsControl
//let popovercontroller = optionsVC.popoverPresentationController
IngredientVC.popoverPresentationController?.delegate = self
IngredientVC.popoverPresentationController?.sourceView = self.view
IngredientVC.popoverPresentationController?.sourceRect = CGRect(x:self.view.frame.width/2, y: self.view.frame.height/2,width: 200,height: 300)
IngredientVC.popoverPresentationController?.permittedArrowDirections = [.down, .up]
// Present the view controller (in a popover).
self.present(IngredientVC, animated: true)
}
Note: I have popover view controller in Storyboard with Simulated Metrics size as "Freeform".
I figured out that all I needed was following function -
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle
{
return UIModalPresentationStyle.none
}
I'm new to Xcode/Swift. I have a center button I want to present a modal view from the current view (tab).
I've implemented a custom class for UITabBar that adds that button according to this guide.
This UITabBar has no view controller in self that I can find, so I get an error when I try to present a view.
Error: Value of type 'MainTabBar' has no member 'present'
Is there a way I can have this button present modal from the current view?
I'm not sure how to reach the current view from this custom class.
Should I point the button's addTarget to a uitabbar Delegate and watch for it in my other views?
Should I stop doing this and just do tabs with a tab that pulls up my modal view in a ViewDidAppear() on a different view?
I think I lose the ability to pop, or dismiss, back to the last view if I do that.
Here's the custom UITabBar class I'm working with.
class MainTabBar: UITabBar {
private var middleButton = UIButton()
override func awakeFromNib() {
super.awakeFromNib()
setupMiddleButton()
}
func setupMiddleButton() {
middleButton.frame.size = CGSize(width: 70, height: 70)
middleButton.backgroundColor = .blue
middleButton.layer.cornerRadius = 35
middleButton.layer.masksToBounds = true
middleButton.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: 0)
middleButton.addTarget(self, action: #selector(test), for: .touchUpInside)
addSubview(middleButton)
}
#objc func test() {
let vc = createController()
vc.modalTransitionStyle = .crossDissolve
self.present(vc, animated: true, completion: nil)
}
}
Found a better implementation.
Created a custom class inherited from UITabBarController and named it MainViewController.
Setup the middle button in that controller instead of in the MainTabBar custom class.
self now has member present.
#objc func test() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc =
storyboard.instantiateViewController(withIdentifier: "createModal") as! createController
self.present(vc, animated: true, completion: nil)
}
https://github.com/drrost/Custom-Tabbar-Cetner-Button
When I'm showing a popover, I expect all views outside the popover to be dimmed. When I create a popover via IB, this works fine. When I create a popover programmatically and call it via an UIBarButtonItem, this doesn't quite work: the back chevron in the navigationbar is not dimmed. Instead, it remains blue:
Code:
class GreenViewController: UIViewController {
private var barButtonItem: UIBarButtonItem!
func barButtonItemAction() {
let blueViewController = BlueViewController()
let navigationController = UINavigationController(rootViewController: blueViewController)
navigationController.modalPresentationStyle = .popover
navigationController.popoverPresentationController?.barButtonItem = self.barButtonItem
self.present(navigationController, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
self.barButtonItem = UIBarButtonItem(title: "Show blue popover", style: .plain, target: self, action: #selector(barButtonItemAction))
self.navigationItem.rightBarButtonItem = barButtonItem
}
}
Why does this happen?
Test project on Github:
https://github.com/bvankuik/TestNavigationBarChevronTint/
I think something may be off in the view hierarchy when the popovercontroller uses the UIBarButtonItem as it's anchor. In InterfaceBuilder, the UIButton is the anchor for the presented popover, and since the UIButton is in the view hierarchy of the presenting view controller, seems to just work.
So I attempted to reproduce some similar conditions by setting the sourceRect and sourceView properties on the popoverPresentationController as follows and it did the trick.
class GreenViewController: UIViewController, UIPopoverPresentationControllerDelegate {
private var barButtonItem: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
barButtonItem = UIBarButtonItem(title: "Show blue popover", style: .plain,
target: self, action: #selector(barButtonItemAction))
navigationItem.rightBarButtonItem = barButtonItem
}
// Defined constants for solution readability
private let sourceRectHeight : CGFloat = 44.0 // NavigationBar Height?
private let sourceRectWidth : CGFloat = 160.0 // UIBarButtonItem Width?
private let sourceRectRightMargin : CGFloat = 20.0 // Right Margin
// This returns the source rect to align our popoverPresentationController
// against, this is pretty much my imaginary frame of the UIBarButtonItem
private var sourceRect : CGRect
{
var rect = navigationController!.navigationBar.frame
rect.origin.x = view.bounds.width - sourceRectWidth - sourceRectRightMargin
rect.origin.y = sourceRectHeight / 2.0
rect.size.width = sourceRectWidth
return rect
}
func barButtonItemAction() {
let blueViewController = BlueViewController()
let navigationController = UINavigationController(rootViewController: blueViewController)
navigationController.modalPresentationStyle = .popover
// Instead of setting the barButtonItem on the popoverPresentationController
// set the srouce view as the root view of the presenting controller
navigationController.popoverPresentationController?.sourceView = view
// Set the source rec to present from, which is calclated relative to the width
// of the current device orientation
navigationController.popoverPresentationController?.sourceRect = sourceRect
// Set self as the delegate for the popoverPresentationController because
// we need to provide a relaculated rect when the device changes orientation
navigationController.popoverPresentationController?.delegate = self
// Present the view controller, and voila :)
self.present(navigationController, animated: true, completion: nil)
}
// UIPopoverPresentationControllerDelegate method that allows us to update
// the source rect of the popover after an orientation change has occurred,
// which calculated relative to with in the sourceRect property above
public func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController,
willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>,
in view: AutoreleasingUnsafeMutablePointer<UIView>)
{
rect.initialize(to: sourceRect)
}
}
Hope this helps :)
I'm just attempting to initiate a simple segue programmatically, but for some reason, when I do so, the destination view is just all black, and when I go back via the navigation controller, it turns the rest of my views black as well. Here is the segue in my IB:
And this the code for the segue, through a button in the master controller:
#IBAction func buttonPressed(sender: AnyObject) {
self.performSegueWithIdentifier("showLoading", sender: self)
}
however when I run the app and press the button, the segue works but the view is just black:
and every view previous is black as well.. but i have notices every time i switch back and forth between the tabs, the views are regular and not this black screen. Why is this unusual behavior occurring?
Once I disable the sizeclass of my storyboard.
Change one of popover segue to push segue.
Then I enable the sizeclass,this segue is the same as yours in storyboard ,so is the problem .
After I change this segue back to popover segue, everything works fine again.
This is how I fix this problem ,but I don't know why.
And the following is my code in presentedviewcontroller:
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == Constants.EditWaypointSegue {
if let waypoint = (sender as? MKAnnotationView)?.annotation as? EditableWaypoint {
if let ewvc = segue.destinationViewController.contentViewController as? EditWaypointViewController {
if let ppc = ewvc.popoverPresentationController {
let coordinatePoint = mapView.convertCoordinate(waypoint.coordinate, toPointToView: mapView)
ppc.sourceRect = (sender as! MKAnnotationView).popoverSourceRectForCoordinatePoint(coordinatePoint)
let minimumSize = ewvc.view.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
ewvc.preferredContentSize = CGSize(width: Constants.EditWaypointPopoverWidth, height: minimumSize.height)
ppc.delegate = self
}
ewvc.waypointToEdit = waypoint
}
}
}
}
// MARK: - UIAdaptivePresentationControllerDelegate
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
println("full")
return UIModalPresentationStyle.FullScreen // full screen, but we can see what's underneath
}
func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController?
{
let navcon = UINavigationController(rootViewController: controller.presentedViewController)
let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .ExtraLight))
visualEffectView.frame = navcon.view.bounds
navcon.view.insertSubview(visualEffectView, atIndex: 0) // "back-most" subview
return navcon
}
I wish to create a small popover about 50x50px from a UIButton. I have seen methods using adaptive segue's but I have my size classes turn of thus meaning I can not use this features!
How else can I create this popover? Can I create it with code inside my button IBACtion? Or is there still a way I can do this with storyboards?
You can do one of the following two options :
Create an action for the UIButton in your UIViewController and inside present the ViewController you want like a Popover and your UIViewController has to implement the protocol UIPopoverPresentationControllerDelegate, take a look in the following code :
#IBAction func showPopover(sender: AnyObject) {
var popoverContent = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardIdentifier") as! UIViewController
popoverContent.modalPresentationStyle = .Popover
var popover = popoverContent.popoverPresentationController
if let popover = popoverContent.popoverPresentationController {
let viewForSource = sender as! UIView
popover.sourceView = viewForSource
// the position of the popover where it's showed
popover.sourceRect = viewForSource.bounds
// the size you want to display
popoverContent.preferredContentSize = CGSizeMake(200,500)
popover.delegate = self
}
self.presentViewController(popoverContent, animated: true, completion: nil)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None
}
According to the book of #matt Programming iOS 8:
A popover presentation controller, in iOS 8, is a presentation controller (UIPresentationController), and presentation controllers are adaptive. This means that, by default, in a horizontally compact environment (i.e. on an iPhone), the .Popover modal presentation style will be treated as .FullScreen. What appears as a popover on the iPad will appear as a fullscreen presented view on the iPhone, completely replacing the interface.
To avoid this behavior in the iPhone you need to implement the delegate method adaptivePresentationStyleForPresentationController inside your UIViewController to display the Popover correctly.
The other way in my opinion is more easy to do, and is using Interface Builder, just arrange from the UIButton to create a segue to the ViewController you want and in the segue select the Popover segue.
I hope this help you.
Swift 4 Here is fully working code. So here you will see popup window with size of 250x250:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// in case if you don't want to make it via IBAction
button.addTarget(self, action: #selector(tapped), for: .touchUpInside)
}
#objc
private func tapped() {
guard let popVC = storyboard?.instantiateViewController(withIdentifier: "popVC") else { return }
popVC.modalPresentationStyle = .popover
let popOverVC = popVC.popoverPresentationController
popOverVC?.delegate = self
popOverVC?.sourceView = self.button
popOverVC?.sourceRect = CGRect(x: self.button.bounds.midX, y: self.button.bounds.minY, width: 0, height: 0)
popVC.preferredContentSize = CGSize(width: 250, height: 250)
self.present(popVC, animated: true)
}
}
// This is we need to make it looks as a popup window on iPhone
extension ViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
Take into attention that you have to provide popVC identifier to one viewController you want to present as a popup.
Hope that helps!
Here you can present a popover on button click.
func addCategory( _ sender : UIButton) {
var popoverContent = self.storyboard?.instantiateViewControllerWithIdentifier("NewCategory") as UIViewController
var nav = UINavigationController(rootViewController: popoverContent)
nav.modalPresentationStyle = UIModalPresentationStyle.Popover
var popover = nav.popoverPresentationController
popoverContent.preferredContentSize = CGSizeMake(50,50)
popover.delegate = self
popover.sourceView = sender
popover.sourceRect = sender.bounds
self.presentViewController(nav, animated: true, completion: nil)
}
Swift 4 Version
Doing most work from the storyboard
I added a ViewController, went to it's attribute inspector and ticked the "Use Preferred Explicit size". After that I changed the Width and Height values to 50 each.
Once this was done I ctrl clicked and dragged from the Button to the ViewController I added choosing "Present as Popover" and naming the segue Identifier as "pop"
Went to the ViewController where I had my Button and added the following code:
class FirstViewController: UIViewController, UIPopoverPresentationControllerDelegate {
#IBOutlet weak var popoverButton: UIButton! // the button
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pop" {
let popoverViewController = segue.destination
popoverViewController.modalPresentationStyle = .popover
popoverViewController.presentationController?.delegate = self
popoverViewController.popoverPresentationController?.sourceView = popoverButton
popoverViewController.popoverPresentationController?.sourceRect = CGRect(x: 0, y: 0, width: popoverButton.frame.size.width, height: popoverButton.frame.size.height)
}
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
override func viewDidLoad() {
super.viewDidLoad()
}
}