I have a project for which I'm using a UIPopoverPresentationController, which I want to show as a popover for both iPhone and iPad. Even in a standalone project with just a blank Storyboard with a single UIButton, the following code keeps resulting in a full-screen modal popup instead. I've seen a question similar to this asked before on SO multiple times, but everyone seems to solve it by returning .None for adaptivePresentationStyleForPresentationController, but even including that isn't fixing it for me. I have a single ViewController with the following code--what am I missing?
import UIKit
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
#IBOutlet weak var thatButton: UIButton!
#IBAction func presentPopoverController(sender: UIButton) {
let popVC = UIViewController() //(nibName: "CameraPopover", bundle: nil)
popVC.view.backgroundColor = UIColor.blueColor()
popVC.modalPresentationStyle = .Popover
popVC.preferredContentSize = CGSize(width: 100, height: 100)
popVC.view.layer.borderWidth = 5
popVC.view.layer.borderColor = UIColor.blackColor().CGColor
self.presentViewController(popVC, animated: true, completion: nil)
let popController = popVC.popoverPresentationController
popController!.delegate = self
}
func prepareForPopoverPresentation(popoverPresentationController: UIPopoverPresentationController) {
popoverPresentationController.permittedArrowDirections = .Down
popoverPresentationController.sourceView = self.thatButton
popoverPresentationController.sourceRect = self.thatButton.frame
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .None
}
}
Related
I have an app reader like Apple Books. And I have a problem. I have NavigationViewController next FirstViewController with book button, SecondViewController it is a reader and TableViewController it is settings for text size. But if I open book in FirstViewController go to SecondViewController and in navigation bar tab aA button and open TableViewController all works fine. But after if I want back to FirstViewController I not close TableViewController and do swipe from left to right my app stops. How to fix it?
SecondViewController:
#IBOutlet weak var textSettings: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setupGestures()
}
// MARK: - Text Settings View
private func setupGestures() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped))
tapGesture.numberOfTapsRequired = 1
textSettings.addGestureRecognizer(tapGesture)
}
#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.textSettings
popOverVC?.sourceRect = CGRect(x: self.textSettings.bounds.midX, y: self.textSettings.bounds.maxY, width: 0, height: 0)
popVC.preferredContentSize = CGSize(width: 250, height: 250)
self.present(popVC, animated: true)
}
}
extension SecondViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
Other controllers have no special code. Video illustrating the problem - https://drive.google.com/file/d/1HUV26H4VFoKglh8FbkNP0y2Y3rYl4Q_f/view?usp=sharing
I’m trying to create a custom, short menu that slides up from the bottom of the screen and stays at the bottom (like the iOS share sheet). I’m having a hard time trying to figure out how to do it. I tried presenting a view controller as a modal and setting the preferred content size, but it still presents it as full screen. How can I present a short, modal-like overlay?
You could use a UIPresentationController and a UIViewControllerTransitioningDelegate.
As a starting point here a few lines of code:
UIViewControllerTransitioningDelegate
class OverlayTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return OverlayPresentationController(presentedViewController:presented, presenting:presenting)
}
}
UIPresentationController
class OverlayPresentationController: UIPresentationController {
private let dimmedBackgroundView = UIView()
private let height: CGFloat = 200.0
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(backgroundTapped))
self.dimmedBackgroundView.addGestureRecognizer(tapGestureRecognizer)
}
override var frameOfPresentedViewInContainerView: CGRect {
var frame = CGRect.zero
if let containerBounds = containerView?.bounds {
frame = CGRect(x: 0,
y: containerBounds.height - height,
width: containerBounds.width,
height: height)
}
return frame
}
override func presentationTransitionWillBegin() {
if let containerView = self.containerView, let coordinator = presentingViewController.transitionCoordinator {
containerView.addSubview(self.dimmedBackgroundView)
self.dimmedBackgroundView.backgroundColor = .black
self.dimmedBackgroundView.frame = containerView.bounds
self.dimmedBackgroundView.alpha = 0
coordinator.animate(alongsideTransition: { _ in
self.dimmedBackgroundView.alpha = 0.5
}, completion: nil)
}
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
self.dimmedBackgroundView.removeFromSuperview()
}
#objc private func backgroundTapped() {
self.presentedViewController.dismiss(animated: true, completion: nil)
}
}
How to call it
let overlayTransitioningDelegate = OverlayTransitioningDelegate()
#IBAction func onOpenModalOverlay(_ sender: Any) {
let overlayVC = OverlayViewController()
overlayVC.transitioningDelegate = self.overlayTransitioningDelegate
overlayVC.modalPresentationStyle = .custom
self.present(overlayVC, animated: true, completion: nil)
}
Demo
The OverlayViewController is a normal ViewController. Here I used an ugly green background color to make it easier to recognize the overlay.
I am having an issue while displaying UITableViewController as a popover. The popover appears but its width in not full screen - there is some kind of margin on leading and trailing side. Check screenshot for more info:
This is the code I am using to setup and present controller:
let controller = UITableViewController()
controller.preferredContentSize = CGSize(width: self.view.bounds.width, height: self.popOverPresentationControllerHeight)
controller.modalPresentationStyle = .popover
controller.tableView.separatorStyle = .none
controller.tableView.allowsMultipleSelection = true
controller.tableView.dataSource = self.filterOptionsDataSource
controller.tableView.delegate = self
controller.tableView.isScrollEnabled = false
controller.tableView.register(FilterOptionCell.self, forCellReuseIdentifier: FilterOptionCell.identifier)
controller.tableView.register(UINib(nibName: FilterOptionCell.identifier, bundle: nil), forCellReuseIdentifier: FilterOptionCell.identifier)
controller.tableView.register(FilterConfirmCell.self, forCellReuseIdentifier: FilterConfirmCell.identifier)
controller.tableView.register(UINib(nibName: FilterConfirmCell.identifier, bundle: nil), forCellReuseIdentifier: FilterConfirmCell.identifier)
controller.popoverPresentationController?.popoverLayoutMargins = UIEdgeInsetsMake(0, 0, 0, 0)
controller.popoverPresentationController?.delegate = self
self.present(controller, animated: true, completion: {
UIView.animate(withDuration: 0.25) {
controller.view.superview?.layer.cornerRadius = 4
self.view.alpha = 0.4
}
})
controller.popoverPresentationController?.backgroundColor = UIColor.white
controller.popoverPresentationController?.sourceView = sender
controller.popoverPresentationController?.sourceRect = sender.bounds
There is also implemented adaptivePresentationStyle function but still, no difference:
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
ADDITIONAL QUESTION
Is there any way top arrow can be changed / modified? This arrow seems a bit too big for my use so is there a way to make it smaller or just replace it with custom one?
EDIT
After some investigating and printing out controller.view.superview?.frame I noticed its width is 20 points smaller than it should be which explains that space on both sides of the popup. But what's the way to get rid of it?
you can try this.
controller.popoverLayoutMargins = UIEdgeInsets(top: 0, left: 1, bottom: 0, right: 1)
Using this property, you can apply a margin from the end of the screen.
https://developer.apple.com/documentation/uikit/uipopovercontroller/1624657-popoverlayoutmargins?language=objc
You should try this code,
override open func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
var size = self.tableView.contentSize
size.width = 150.0 //Here, you can set the width.
self.preferredContentSize = size
}
and for brief understanding, how i create and present a popoverController.
#objc open static func instantiate() -> PopOverViewController {
let storyboardsBundle = getStoryboardsBundle() //Or Bundle.main
let storyboard:UIStoryboard = UIStoryboard(name: "PopOver", bundle: storyboardsBundle)
let popOverViewController:PopOverViewController = storyboard.instantiateViewController(withIdentifier: "PopOverViewController") as! PopOverViewController
popOverViewController.modalPresentationStyle = UIModalPresentationStyle.popover
popOverViewController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.up
// arrow color
popOverViewController.popoverPresentationController?.backgroundColor = UIColor.darkGray
return popOverViewController
}
To Present in a Specific ViewController,
let popover = PopOverViewController.instantiate()
popover.popoverPresentationController?.sourceView = self.underlyingTextView
popover.popoverPresentationController?.sourceRect = rect
popover.presentationController?.delegate = self
viewController.present(popover, animated: true, completion: nil)
//MARK: UIAdaptivePresentationControllerDelegate
public func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
public func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
In ViewDidLoad(), you can configure your tableView.
I have a class to construct a UIPopoverPresentationController in an empty swift file:
import Foundation
import UIKit
class Popovers : NSObject, UIPopoverPresentationControllerDelegate {
func presentLoginScreen() {
// Presenting login Popover
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginScreen = storyboard.instantiateViewControllerWithIdentifier("LogInScreen")
let window = UIApplication.sharedApplication().windows.last!
let height = window.screen.bounds.size.height
let width = window.screen.bounds.size.width
loginScreen.modalPresentationStyle = UIModalPresentationStyle.Popover
loginScreen.preferredContentSize = CGSizeMake(width * 0.85 , height * 0.35)
let loginScreenPopover = loginScreen.popoverPresentationController
loginScreenPopover?.permittedArrowDirections = UIPopoverArrowDirection.init(rawValue: 0)
loginScreenPopover?.delegate = self
loginScreenPopover?.sourceView = window.rootViewController?.view
loginScreenPopover?.sourceRect = CGRectMake(CGRectGetMidX(window.screen.bounds), CGRectGetMidY(window.screen.bounds), 0, 0)
window.rootViewController?.presentViewController(loginScreen, animated: true, completion: nil)
let blur = UIVisualEffectView(effect: UIBlurEffect(style: .Light)) as UIVisualEffectView
let bounds = window.screen.bounds
blur.frame = bounds
blur.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
window.rootViewController!.view.addSubview(blur)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
func popoverPresentationControllerDidDismissPopover(popoverPresentationController: UIPopoverPresentationController) {
// Remove blur layer
print("method called")
let window = UIApplication.sharedApplication().windows.last
window?.subviews.last?.removeFromSuperview()
}
}
But whenever I dismiss the PopoverPresentationController the delegate method popoverPresentationControllerDidDismissPopover is never fired.
I have set the delegate of the loginScreenPopover to self and let the Popovers class inherit from the UIPopoverPresentationControllerDelegate but neither seems to be working.
It is on an iPhone, if that is relevant to the question.
I have found the answer. In the ViewDidLoad() from the LogInScreen I set the delegate to:
self.popoverPresentationController.delegate = getPopovers()
Where the getPopovers() function is a function to retrieve the Popover object created somewhere else in the code. Now in my Popovers class I can conform to the UIPopoverPresentationControllerDelegate and use those methods.
How can i create a custom alert with Swift? I try translating a guide from Objective c but loads a full screen layout
for do it easy i can load a new layout with the transparent background i try this:
listaalertviewcontroller.view.backgroundColor = UIColor.clearColor()
let purple = UIColor.purpleColor() // 1.0 alpha
let semi = purple.colorWithAlphaComponent(0.5)
listaalertviewcontroller.view.backgroundColor = semi
presentingViewController.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
self.presentViewController(listaalertviewcontroller, animated: true, completion: nil)
in the animation it's transparent but when the animation ends it's opaque... and i turn off opaque option in the view... what i'm doing wrong?
Code tested in Swift 5 and Xcode 10
How to make your own custom Alert
I was wanting to do something similar. First of all, UIAlertView is deprecated in favor of UIAlertController. See this answer for the standard way to display an alert:
How would I create a UIAlertView in Swift?
And both UIAlertView and UIAlertController do not really allow much customization. One option is to use some third party code. However, I discovered that it isn't that difficult to create your own Alert by displaying another view controller modaly.
The example here is just a proof-of-concept. You can design your alert any way you want.
Storyboard
You should have two View Controllers. Your second view controller will be your alert. Set the class name to AlertViewContoller and the Storyboard ID to alert. (Both of these are names that we defined ourselves in the code below, nothing special about them. You can add the code first if you want. It might actually be easier if you add the code first.)
Set the background color for the root view (in your Alert View Controller) to clear (or translucent black is nice for an alert). Add another UIView and center it with constraints. Use that as your alert background and put whatever you want inside. For my example, I added a UIButton.
Code
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBAction func showAlertButtonTapped(_ sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myAlert = storyboard.instantiateViewController(withIdentifier: "alert")
myAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(myAlert, animated: true, completion: nil)
}
}
AlertViewController.swift
import UIKit
class AlertViewController: UIViewController {
#IBAction func dismissButtonTapped(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
}
Don't forget to hook up the outlets.
You can add an onTouchUp event listener to the background view to dismiss the popup when the user clicks outside of it.
That's it. You should be able to make any sort of alert that you can imagine now. No need for third party code.
Here is another custom alert I made. Still ugly, but it shows more things you can do.
Other options
Sometimes there is no need to reinvent the wheel, though. I'm impressed with the third party project SDCAlertView (MIT license). It is written in Swift but you can use it with Objective-C projects as well. It offers a wide range of customability.
Here is the Swift 3 code. Thanks a lot #Suragch for the awesome approach to create a custom AlertView.
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBAction func showAlertButtonTapped(sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let myAlert = storyboard.instantiateViewController(withIdentifier: "storyboardID")
myAlert.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext
myAlert.modalTransitionStyle = UIModalTransitionStyle.crossDissolve
self.present(myAlert, animated: true, completion: nil)
}
AlertViewController.swift
import UIKit
class AlertViewController: UIViewController {
#IBAction func dismissButtonTapped(sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
}
To make it a little more interesting or to make the default effect in iOS, you could add either a VisualEffectView or change the color of the main UIView to a dark color and set its alpha to 70%. I prefer the second approach since the blur effect is not as smooth as the one with the view with 70 alpha.
Effect with VisualEffectView:
Effect using a UIView with 70 Alpha:
Nowadays, an alert is merely a simple presented view controller. You can write a presented view controller that behaves similarly to an alert — that is, it pops onto the screen and dims whatever is behind it — but it's your view controller and you are free to give it any interface you like.
To get you started, I've written a github project that you can download and run, and modify to suit your actual needs.
I'll show the key part of the code. The "alert" view controller, in its initializers, sets its own modal presentation style as custom and sets a transitioning delegate:
class CustomAlertViewController : UIViewController {
let transitioner = CAVTransitioner()
override init(nibName: String?, bundle: Bundle?) {
super.init(nibName: nibName, bundle: bundle)
self.modalPresentationStyle = .custom
self.transitioningDelegate = self.transitioner
}
convenience init() {
self.init(nibName:nil, bundle:nil)
}
required init?(coder: NSCoder) {
fatalError("NSCoding not supported")
}
}
All the work is done by the transitioning delegate:
class CAVTransitioner : NSObject, UIViewControllerTransitioningDelegate {
func presentationController(
forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController)
-> UIPresentationController? {
return MyPresentationController(
presentedViewController: presented, presenting: presenting)
}
}
class MyPresentationController : UIPresentationController {
func decorateView(_ v:UIView) {
// iOS 8 doesn't have this
// v.layer.borderColor = UIColor.blue.cgColor
// v.layer.borderWidth = 2
v.layer.cornerRadius = 8
let m1 = UIInterpolatingMotionEffect(
keyPath:"center.x", type:.tiltAlongHorizontalAxis)
m1.maximumRelativeValue = 10.0
m1.minimumRelativeValue = -10.0
let m2 = UIInterpolatingMotionEffect(
keyPath:"center.y", type:.tiltAlongVerticalAxis)
m2.maximumRelativeValue = 10.0
m2.minimumRelativeValue = -10.0
let g = UIMotionEffectGroup()
g.motionEffects = [m1,m2]
v.addMotionEffect(g)
}
override func presentationTransitionWillBegin() {
self.decorateView(self.presentedView!)
let vc = self.presentingViewController
let v = vc.view!
let con = self.containerView!
let shadow = UIView(frame:con.bounds)
shadow.backgroundColor = UIColor(white:0, alpha:0.4)
shadow.alpha = 0
con.insertSubview(shadow, at: 0)
shadow.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let tc = vc.transitionCoordinator!
tc.animate(alongsideTransition: { _ in
shadow.alpha = 1
}) { _ in
v.tintAdjustmentMode = .dimmed
}
}
override func dismissalTransitionWillBegin() {
let vc = self.presentingViewController
let v = vc.view!
let con = self.containerView!
let shadow = con.subviews[0]
let tc = vc.transitionCoordinator!
tc.animate(alongsideTransition: { _ in
shadow.alpha = 0
}) { _ in
v.tintAdjustmentMode = .automatic
}
}
override var frameOfPresentedViewInContainerView : CGRect {
// we want to center the presented view at its "native" size
// I can think of a lot of ways to do this,
// but here we just assume that it *is* its native size
let v = self.presentedView!
let con = self.containerView!
v.center = CGPoint(x: con.bounds.midX, y: con.bounds.midY)
return v.frame.integral
}
override func containerViewWillLayoutSubviews() {
// deal with future rotation
// again, I can think of more than one approach
let v = self.presentedView!
v.autoresizingMask = [
.flexibleTopMargin, .flexibleBottomMargin,
.flexibleLeftMargin, .flexibleRightMargin
]
v.translatesAutoresizingMaskIntoConstraints = true
}
}
extension CAVTransitioner { // UIViewControllerTransitioningDelegate
func animationController(
forPresented presented:UIViewController,
presenting: UIViewController,
source: UIViewController)
-> UIViewControllerAnimatedTransitioning? {
return self
}
func animationController(
forDismissed dismissed: UIViewController)
-> UIViewControllerAnimatedTransitioning? {
return self
}
}
extension CAVTransitioner : UIViewControllerAnimatedTransitioning {
func transitionDuration(
using transitionContext: UIViewControllerContextTransitioning?)
-> TimeInterval {
return 0.25
}
func animateTransition(
using transitionContext: UIViewControllerContextTransitioning) {
let con = transitionContext.containerView
let v1 = transitionContext.view(forKey: .from)
let v2 = transitionContext.view(forKey: .to)
// we are using the same object (self) as animation controller
// for both presentation and dismissal
// so we have to distinguish the two cases
if let v2 = v2 { // presenting
con.addSubview(v2)
let scale = CGAffineTransform(scaleX: 1.6, y: 1.6)
v2.transform = scale
v2.alpha = 0
UIView.animate(withDuration: 0.25, animations: {
v2.alpha = 1
v2.transform = .identity
}) { _ in
transitionContext.completeTransition(true)
}
} else if let v1 = v1 { // dismissing
UIView.animate(withDuration: 0.25, animations: {
v1.alpha = 0
}) { _ in
transitionContext.completeTransition(true)
}
}
}
}
It looks like a lot of code, and I suppose it is, but it's almost entire confined to a single class, which is entirely boilerplate; just copy and paste. All you have to do is write the internal interface and behavior of your "alert" view controller, giving it buttons and text and whatever else you want, just as you would do for any other view controller.
Custom Alert UIView Class in swift 4. And Usage ##
import UIKit
class Dialouge: UIView {
#IBOutlet weak var lblTitle: UILabel!
#IBOutlet weak var lblDescription: UILabel!
#IBOutlet weak var btnLeft: UIButton!
#IBOutlet weak var btnRight: UIButton!
#IBOutlet weak var viewBg: UIButton!
var leftAction = {}
var rightAction = {}
override func draw(_ rect: CGRect)
{
self.btnRight.layer.cornerRadius = self.btnRight.frame.height/2
self.btnLeft.layer.cornerRadius = self.btnLeft.frame.height/2
self.btnLeft.layer.borderWidth = 1.0
self.btnLeft.layer.borderColor = #colorLiteral(red: 0.267678082, green: 0.2990377247, blue: 0.7881471515, alpha: 1)
}
#IBAction func leftAction(_ sender: Any) {
leftAction()
}
#IBAction func rightAction(_ sender: Any) {
rightAction()
}
#IBAction func bgTapped(_ sender: Any) {
self.removeFromSuperview()
}
}
strong text
## Usage Of Custom Alert with Tabbar.
let custView = Bundle.main.loadNibNamed("Dialouge", owner: self, options:
nil)![0] as? Dialouge
custView?.lblDescription.text = "Are you sure you want to delete post?"
custView?.lblTitle.text = "Delete Post"
custView?.btnLeft.setTitle("Yes", for: .normal)
custView?.btnRight.setTitle("No", for: .normal)
custView?.leftAction = {
self.deletePost(postId: self.curr_post.id,completion: {
custView?.removeFromSuperview()
})
}
custView?.rightAction = {
custView?.removeFromSuperview()
}
if let tbc = self.parentt?.tabBarController {
custView?.frame = tbc.view.frame
DispatchQueue.main.async {
tbc.view.addSubview(custView!)
}
}else if let tbc = self.parView?.parenttprof {
custView?.frame = tbc.view.frame
DispatchQueue.main.async {
tbc.view.addSubview(custView!)
}
}
else
{
custView?.frame = self.parView?.view.frame ?? CGRect.zero
DispatchQueue.main.async {
self.parView?.view.addSubview(custView!)
}
}
Use https://github.com/shantaramk/Custom-Alert-View
It is effortless to implement this. Follow the steps below:
Drag down the AlertView folder in project directory
Show AlertView Popup
func showUpdateProfilePopup(_ message: String) {
let alertView = AlertView(title: AlertMessage.success, message: message, okButtonText: LocalizedStrings.okay, cancelButtonText: "") { (_, button) in
if button == .other {
self.navigationController?.popViewController(animated: true)
}
}
alertView.show(animated: true)
}