My App is crashed when I try to call Custom UIPresentationController from UICollectionViewController
#IBAction func showSettingsMenu(sender: UIBarButtonItem) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let mvc = storyboard.instantiateViewControllerWithIdentifier(Constants.SettingsViewControllerId) as? SettingsViewController {
mvc.modalPresentationStyle = .Custom
mvc.transitioningDelegate = self
presentViewController(mvc, animated: true, completion: nil)
}
}
here is my Custom Presentation View Controller:
class SpecialSizePresentationController: UIPresentationController {
override func frameOfPresentedViewInContainerView() -> CGRect {
guard let height = containerView?.bounds.height else {
return CGRectZero
}
guard let width = containerView?.bounds.width else {
return CGRectZero
}
let y = height * 0.6
let frameHeight = height * 0.4
return CGRectMake(8, y - 8 , width - 8 - 8 , frameHeight)
}
}
here is UIViewControllerTransitioningDelegate protocol implementation in my UICollectionViewController:
func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? {
return SpecialSizePresentationController(presentedViewController: presented, presentingViewController: presenting)
}
and app is crashed in return place with error (due to presenting is nil):
fatal error: unexpectedly found nil while unwrapping an Optional value
Is it possible to present custom Presentation Controller from UICollectionViewController?
How it can be implemented?
I solved this issue, using source instaed of presenting and error was gone
Related
I have some problems displaying a viewcontroller in my IOS app.
Sometimes it works and the view is displayed, but sometimes and I guess when the context is a bit different it will not work. No errors or warnings in the debugger and it can find the ViewController from the Main storyboard (at least it is not nil)
It use to work with self.present but that seems not to work anymore.
#IBAction func showHistoryButton(_ sender: MDCButton) {
let exercisesHistoryVC = ExercisesHistoryViewController.instantiate(from: .Main)
exercisesHistoryVC.modalPresentationStyle = .fullScreen
let appDeligate = UIApplication.shared.delegate as! AppDelegate
appDeligate.window?.rootViewController!.present(exercisesHistoryVC,animated: true,completion: nil)
// parent?.present(exercisesHistoryVC, animated: true, completion: nil)
}
Use the code like below,while Present New View Controller
#IBAction func showHistoryButton(_ sender: MDCButton) {
let exercisesHistoryVC = ExercisesHistoryViewController.instantiate(from: .Main)
exercisesHistoryVC.modalPresentationStyle = .fullScreen
UIApplication.topViewController()?.present(exercisesHistoryVC, animated: false, completion: nil)
}
extension UIApplication {
static func topViewController(base: UIViewController? = UIApplication.shared.delegate?.window??.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
return topViewController(base: selected)
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
I am attempting to create an 'alert banner' which displays a pin in a label within the alert banner.
I am calling the alert banner from 'ViewController.swift' with:
AlertBanner().Show(self, pin: 2520)
Fatal error: Unexpectedly found nil while implicitly unwrapping an
Optional value
The error is telling me that the AlertBannerText label is nil. I suspect it has not been created yet, but how would I go about waiting for the label to exist before populating it?
Any the code in the AlertBanner class:
class AlertBanner : UIView {
#IBOutlet var AlertBannerText: UILabel!
static weak var delegate: AlertBannerDelegate!
func Show(_ vc: ViewController, pin: Int){
var onlineBanner: UIView?
var topPadding: CGFloat?
var menuView: UIView?
AlertBannerText.text = "\(pin)"
let window = UIApplication.shared.keyWindow!
if let menu = vc.view.viewWithTag(3) { menuView = menu}
onlineBanner = UINib(nibName: "AlertBanner", bundle: nil).instantiate(withOwner: nil, options: nil).first as? AlertBanner
if #available(iOS 11.0, *) { topPadding = window.safeAreaInsets.top }
onlineBanner?.frame.origin.y = 0 - (window.frame.size.width / 7.5 + topPadding!)
onlineBanner!.frame.size.width = window.frame.size.width
onlineBanner!.frame.size.height = window.frame.size.width / 5
onlineBanner!.tag = 2
vc.view.insertSubview(onlineBanner!, belowSubview: menuView!)
if let banner = vc.view.viewWithTag(2) {
UIView.animate(withDuration: 0.5, delay: 0.0, options: [], animations: {
banner.frame.origin.y = window.frame.size.width / 7.5 + topPadding!
}, completion: nil)
}
}
Doing this
AlertBanner().//////
creates an instance without the layout , hence all outlets are nil , you need to create an instance with
class func getInstance() -> AlertBanner {
let onlineBanner = UINib(nibName: "AlertBanner", bundle: nil).instantiate(withOwner: nil, options: nil).first as! AlertBanner
return onlineBanner
}
and then
AlertBanner.getInstance().//////
In the project shown below there is an InitialViewController that has a single button labeled "Show Popover". When that button is tapped the app is supposed to present the second view controller (PopoverViewController) as a popover. The second view controller just has a label saying "Popover!".
This works fine if the InitialViewController takes care of instantiating PopoverViewController, retrieving the popoverPresentationController and then setting the popoverPresentationController's delegate to itself (to InitialViewController). You can see the result, below:
For maximum reusability, however, it would be great if the InitialViewController did not need to know anything about how the presentation controller is delegated. I think it should be possible for the PopoverViewController to set itself as the popoverPresentationController's delegate. I've tried this in either the viewDidLoad or the viewWillAppear functions of the PopoverViewController. However, the PopoverViewController is presented modally in both cases, as shown below:
All the code is contained in just the InitialViewController and the PopoverViewController. The code used in the failing version of the InitialViewController is shown below:
import UIKit
// MARK: - UIViewController subclass
class InitialViewController: UIViewController {
struct Lets {
static let storyboardName = "Main"
static let popoverStoryboardID = "Popover View Controller"
}
#IBAction func showPopoverButton(_ sender: UIButton) {
// instantiate & present the popover view controller
let storyboard = UIStoryboard(name: Lets.storyboardName,
bundle: nil )
let popoverViewController =
storyboard.instantiateViewController(withIdentifier: Lets.popoverStoryboardID )
popoverViewController.modalPresentationStyle = .popover
guard let popoverPresenter = popoverViewController.popoverPresentationController
else {
fatalError( "could not retrieve a pointer to the 'popoverPresentationController' property of popoverViewController")
}
present(popoverViewController,
animated: true,
completion: nil )
// Retrieve and configure UIPopoverPresentationController
// after presentation (per
// https://developer.apple.com/documentation/uikit/uipopoverpresentationcontroller)
popoverPresenter.permittedArrowDirections = .any
let button = sender
popoverPresenter.sourceView = button
popoverPresenter.sourceRect = button.bounds
}
}
The code in the failing PopoverViewController is shown below:
import UIKit
// MARK: - main UIViewController subclass
class PopoverViewController: UIViewController {
// MARK: API
var factorForMarginsAroundButton: CGFloat = 1.2
// MARK: outlets and actions
#IBOutlet weak var popoverLabel: UILabel!
// MARK: lifecycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear( animated )
// set the preferred size for popover presentations
let labelSize =
popoverLabel.systemLayoutSizeFitting( UILayoutFittingCompressedSize )
let labelWithMargins =
CGSize(width: labelSize.width * factorForMarginsAroundButton,
height: labelSize.height * factorForMarginsAroundButton )
preferredContentSize = labelWithMargins
// set the delegate for the popoverPresentationController to self
popoverPresentationController?.delegate = self
}
}
// MARK: - UIPopoverPresentationControllerDelegate
// (inherits from protocol UIAdaptivePresentationControllerDelegate)
extension PopoverViewController: UIPopoverPresentationControllerDelegate
{
func adaptivePresentationStyle(for controller: UIPresentationController,
traitCollection: UITraitCollection)
-> UIModalPresentationStyle{
return .none
}
}
Is it possible for a view controller that is being presented as a popover to be the delegate for its own popoverPresentationController?
I'm using Xcode 8.0, Swift 3.1 and the target is iOS 10.0
It's certainly possible. You're dealing with a timing issue. You need to set the delegate before viewWillAppear. Unfortunately, there is no convenient view lifecycle function to insert the assignment into, so I did this instead.
In your PopoverViewController class, assign the delegate in an overriden getter. You can make the assignment conditional if you'd like. This creates a permanent relationship, so other code code never "override" the delegate by assigning it.
override var popoverPresentationController: UIPopoverPresentationController? {
get {
let ppc = super.popoverPresentationController
ppc?.delegate = self
return ppc
}
}
As #allenh has correctly observed, you need to set the delgate before viewWillAppear, and he has offered a clever solution by setting the delegate by overriding the popoverPresentationController getter.
You could also set the delegate to the popover itself in your showPopover() function between setting modalPresentationStyle and presenting the popover:
let vc = storyboard.instantiateViewController(withIdentifier: Lets.popoverStoryboardID )
vc.modalPresentationStyle = .popover
vc.popoverPresentationController?.delegate = vc
present(vc, animated: true, completion: nil)
I'm trying to realize the Observer Pattern and I'm experiencing some difficulty as my delegate doesn't seem to be setting properly.
In my Main.storyboard I have a ViewController with a container view. I also have an input box where I'm capturing numbers from a number keypad.
Here's my storyboard:
I'm trying to implement my own Observer Pattern using a protocol that looks like this:
protocol PropertyObserverDelegate {
func willChangePropertyValue(newPropertyValue:Int)
func didChangePropertyValue(oldPropertyValue:Int)
}
My main ViewController.swift
class ViewController: UIViewController {
#IBOutlet weak var numberField: UITextField!
// observer placeholder to be initialized in implementing controller
var observer : PropertyObserverDelegate?
var enteredNumber: Int = 0 {
willSet(newValue) {
print("//Two: willSet \(observer)") // nil !
observer?.willChangePropertyValue(5) // hard coded value for testing
}
didSet {
print("//Three: didSet")
observer?.didChangePropertyValue(5) // hard coded value for testing
}
}
#IBAction func numbersEntered(sender: UITextField) {
guard let inputString = numberField.text else {
return
}
guard let number : Int = Int(inputString) else {
return
}
print("//One: \(number)")
self.enteredNumber = number // fires my property observer
}
}
My ObservingViewController:
class ObservingViewController: UIViewController, PropertyObserverDelegate {
// never fires!
func willChangePropertyValue(newPropertyValue: Int) {
print("//four")
print(newPropertyValue)
}
// never fires!
func didChangePropertyValue(oldPropertyValue: Int) {
print("//five")
print(oldPropertyValue)
}
override func viewDidLoad() {
super.viewDidLoad()
print("view loads")
// attempting to set my delegate
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let pvc = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController") as! ViewController
print("//six \(pvc)")
pvc.observer = self
}
}
Here's what my console prints:
What's happening
As you can see when my willSet fires, my observer is nil which indicates that I have failed to set my delegate in my ObservingViewController. I thought I set my delegate using these lines:
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let pvc = mainStoryboard.instantiateViewControllerWithIdentifier("ViewController") as! ViewController
print("//six \(pvc)")
pvc.observer = self
However, I must be setting my delegate incorrectly if it's coming back nil.
Question
How do I properly set my delegate?
You are calling into the storyboard to instantiate a view controller and setting it as the observer, however that instantiates a new instance of that view controller, it doesn't mean that it is referencing the one single "view controller" that is in the storyboard. ObservingViewController needs another way to reference the ViewController that has already been created.
So #Chris did reenforce my suspicions which helped me to figure out a solution for assigning my delegate to my view controller properly.
In my ObservingViewController I just need to replace the code in my viewDidLoad with the following:
override func viewDidLoad() {
let app = UIApplication.sharedApplication().delegate as! AppDelegate
let vc = app.window?.rootViewController as! ViewController
vc.observer = self
}
Rather than creating a new instance of my view controller, I'm now getting my actual view controller.
I'm having some trouble getting a UIPopover to appear using swift. The code that is commented out works fine in Objective-C, but doesn't work using Swift. When I tap the + in my view controller I do get the "click" in my debugger, however no popover appears.
class PlayerInformationTableViewController: UITableViewController, NSFetchedResultsControllerDelegate, UIPopoverControllerDelegate {
#IBOutlet weak var addBarButtonItem: UIBarButtonItem!
var playerInformationViewController = PlayerInformationViewController()
var popover:UIPopoverController? = nil
override func viewDidLoad() {
super.viewDidLoad()
/*
//setup the popover
_cuesPopoverViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"CuesPopoverViewController"];
self.cuesPopover = [[UIPopoverController alloc] initWithContentViewController:_cuesPopoverViewController];
self.cuesPopover.popoverContentSize = CGSizeMake(540, 300);
self.cuesPopover.delegate = self;
*/
playerInformationViewController.storyboard?.instantiateViewControllerWithIdentifier("PlayerInformationViewController")
popover?.contentViewController = playerInformationViewController
popover?.popoverContentSize = CGSizeMake(300, 300)
popover?.delegate = self
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
#IBAction func addPopover(sender: AnyObject) {
println("Click")
popover?.presentPopoverFromBarButtonItem(addBarButtonItem, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
}
Solution
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addPopover(sender: AnyObject) {
var popoverViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PlayerInformationViewController") as UIViewController
popoverViewController.modalPresentationStyle = .Popover
popoverViewController.preferredContentSize = CGSizeMake(450, 450)
let popoverPresentationViewController = popoverViewController.popoverPresentationController
popoverPresentationViewController?.permittedArrowDirections = .Any
popoverPresentationViewController?.delegate = self
popoverPresentationViewController?.barButtonItem = sender as UIBarButtonItem
presentViewController(popoverViewController, animated: true, completion: nil)
}
Here is a simple example for iOS 8. Popover are presented using adaptivity apis in iOS 8.
class PlayerInformationTableViewController: UITableViewController, UIPopoverPresentationControllerDelegate, NSFetchedResultsControllerDelegate{
...
#IBAction func addPopover(sender: UIBarButtonItem){
let playerInformationViewController = PlayerInformationViewController()
playerInformationViewController.modalPresentationStyle = .Popover
playerInformationViewController.preferredContentSize = CGSizeMake(300, 300)
let popoverPresentationViewController = playerInformationViewController.popoverPresentationController
popoverPresentationViewController?.permittedArrowDirections = .Any
popoverPresentationViewController?.delegate = self
popoverPresentationController?.barButtonItem = sender
presentViewController(playerInformationViewController, animated: true, completion: nil)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController!) -> UIModalPresentationStyle{
return .None
}
}
Display Popover with contentView from xib
func showPopover(sender: AnyObject) {
let contentViewController = UINib(nibName: "ContentVC", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as ContentVC
contentViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
var detailPopover: UIPopoverPresentationController = contentViewController.popoverPresentationController!
detailPopover.delegate = self
detailPopover.barButtonItem = sender as UIBarButtonItem
detailPopover.permittedArrowDirections = UIPopoverArrowDirection.Any
presentViewController(contentViewController,
animated: true, completion:nil)
}
Next allows to make not full screen PopoverView on iPhone
for this do not forget to inherit MainViewController: UIPopoverPresentationControllerDelegate and set delegate to PopoverView
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController!) -> UIModalPresentationStyle
{
return .None
}
It looks like your popover is nil. Where are you assigning/instantiating it?
Try changing this:
popover?.presentPopoverFromBarButtonItem(addBarButtonItem, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
To this:
if let pop = popover {
pop.presentPopoverFromBarButtonItem(addBarButtonItem, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
} else {
NSLog("Error: Popover was nil")
}
I imagine you'll see that error message in your console. In the .XIB for your PlayerInformationTableViewController, do you have a UIPopoverController?
If so, you probably need to ensure that the var popover is either (1) being manually instantiated in your awakeFromNib, or that it's an #IBOutlet and is being connected properly.
Alternatively, can you simply use the popover already present in your playerInformationViewController?