UIPopoverArrowDirection.down is not working for iOS - ios

I am trying to present popover viewcontroller with down arrow over my Tabbarview controller, but it is stopped appearing if i choose down, and in case of up it is displaying.
So my questions are
1)How to make arrow down if UIPopoverArrowDirection.down is not working ? And why is it not working ?
2) How to debug and find the error that why it is not displaying, there are no logs been printed for this.
3)How to display Arrow in centre of the view not in the side.
my code is,
let popController = UIStoryboard(name: kStoryboard.login, bundle: nil).instantiateViewController(withIdentifier: "popoverId")
popController.modalPresentationStyle = UIModalPresentationStyle.popover
popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.down
popController.popoverPresentationController?.backgroundColor = UIColor.Text.orange
popController.popoverPresentationController?.delegate = self
popController.popoverPresentationController?.sourceView = self.view
popController.popoverPresentationController?.sourceRect = CGRect(x: 0, y: 0, width: 100, height: 100)
// present the popover
self.present(popController, animated: true, completion: nil)
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle
{
return UIModalPresentationStyle.none
}
I have given preferredcontentsize from storyboard.

The problem is that currently you are not providing a sourceView + preferredContentSize
import UIKit
class ViewController: UIViewController , UIPopoverPresentationControllerDelegate{
#IBOutlet weak var aa: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool)
{
let popController = UIStoryboard(name:"Main", bundle: nil).instantiateViewController(withIdentifier: "popoverId")
popController.modalPresentationStyle = UIModalPresentationStyle.popover
popController.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.down
popController.popoverPresentationController?.backgroundColor = UIColor.red
popController.popoverPresentationController?.delegate = self
popController.popoverPresentationController?.sourceView = self.aa
popController.popoverPresentationController?.sourceRect = CGRect(x: 20, y: 20, width: 100, height: 100)
popController.preferredContentSize = CGSize.init(width: 200, height: 200)
// present the popover
self.present(popController, animated: true, completion: nil)
}
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle
{
return UIModalPresentationStyle.none
}
}

Related

How to build a context menu like Facebook / Slack on iOS?

I was just looking at Context menu of Facebook and or slack and wanted to create something similar in my App.
I have tried two methods.
First method. Having a in View Table View and sliding it from bottom to create as if it is animated on to the view. But the problem with this is that The navigation controller and Tab bar controller are not hidden and a white patch is shown over the Black (Alpha 30 %).
The second method I tried was showing a new View controller over the current view controller and presenting as a Modal presentation.
let vc = CustomActionTableViewController(nibName: "CustomActionTableViewController", bundle: nil)
vc.modalPresentationStyle = .overFullScreen
self.present(vc, animated: false, completion: nil)
This works okay but the method is too slow as I have to work with lot of Notifications (To send selected index to my main View and then perform action). It is painfully slow.
Could anyone help me with how I can improve the implementation so that I can get the Action sheet similar to Facebook which is smooth and very very fluid
Check this example : Bottom pop Up
Currently I am using this in my app and it's work fine.
Since you mentioned Slack, they actually have open sourced their bottom sheet implementation, PanModal.
Using UIPresentationController and UIPanGestureRecognizer
1- create BottomMenu presentation Controller which will handle the height of your View Controller and blur
class BottomMenuPresentationController: UIPresentationController {
// MARK: - Properties
var blurEffectView: UIVisualEffectView?
var tapGestureRecognizer = UITapGestureRecognizer()
private var topHeightRatio: Float
private var bottomHeightRatio: Float
init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, topHeightRatio: Float, bottomHeightRatio: Float) {
let blurEffect = UIBlurEffect(style: .systemThickMaterialDark)
blurEffectView = UIVisualEffectView(effect: blurEffect)
self.topHeightRatio = topHeightRatio
self.bottomHeightRatio = bottomHeightRatio
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
blurEffectView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissController))
self.blurEffectView?.isUserInteractionEnabled = true
self.blurEffectView?.addGestureRecognizer(tapGestureRecognizer)
}
override var frameOfPresentedViewInContainerView: CGRect {
CGRect(origin: CGPoint(x: 0, y: self.containerView!.frame.height * CGFloat(topHeightRatio)),
size: CGSize(width: self.containerView!.frame.width, height: self.containerView!.frame.height * CGFloat(bottomHeightRatio)))
}
override func presentationTransitionWillBegin() {
self.blurEffectView?.alpha = 0
if let blurEffectView = blurEffectView {
self.containerView?.addSubview(blurEffectView)
}
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (_) in
self.blurEffectView?.alpha = 0.66
}, completion: { (_) in })
}
override func dismissalTransitionWillBegin() {
self.presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (_) in
self.blurEffectView?.alpha = 0
}, completion: { (_) in
self.blurEffectView?.removeFromSuperview()
})
}
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
presentedView!.roundCorners([.topLeft, .topRight], radius: 14)
}
override func containerViewDidLayoutSubviews() {
super.containerViewDidLayoutSubviews()
presentedView?.frame = frameOfPresentedViewInContainerView
blurEffectView?.frame = containerView!.bounds
}
#objc func dismissController() {
self.presentedViewController.dismiss(animated: true, completion: nil)
}
}
2- create Your ViewController
class BottomMenuVC: UIViewController {
// MARK: - Instances
var hasSetPointOrigin = false
var pointOrigin: CGPoint?
// MARK: - Properties
let topDarkLine: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hexString: "#E1E1E1")
view.layer.cornerRadius = 2
return view
}()
let cancelButn: UIButton = {
let button = UIButton(type: .custom)
button.setAttributedTitle(NSAttributedString(string: "Cancel", attributes: [NSAttributedString.Key.font: UIFont.LatoMedium(size: 17),
NSAttributedString.Key.foregroundColor: UIColor(hexString: "#515151")
]), for: .normal)
button.backgroundColor = UIColor(hexString: "#F1F3F4")
button.layer.cornerRadius = 5.0
button.addTarget(self, action: #selector(cancelButnPressed), for: .touchUpInside)
return button
}()
// MARK: - viewLifeCycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.isUserInteractionEnabled = true
setupMenuView()
}
override func viewDidLayoutSubviews() {
if !hasSetPointOrigin {
hasSetPointOrigin = true
pointOrigin = self.view.frame.origin
}
}
// MARK: - SetupView
func setupMenuView() {
self.view.addSubview(topDarkLine)
self.view.addSubview(cancelButn)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognizerAction(_:)))
view.addGestureRecognizer(panGesture)
topDarkLine.constrainHeight(constant: 4)
topDarkLine.constrainWidth(constant: view.frame.size.width * 0.10)
topDarkLine.centerXInSuperview()
topDarkLine.anchor(top: view.topAnchor, leading: nil, bottom: nil, trailing: nil, padding: .init(top: 8, left: 0, bottom: 0, right: 0))
cancelButn.anchor(top:view.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor,
padding: .init(top: 16, left: 16, bottom: 0, right: 16))
cancelButn.constrainHeight(constant: 44)
}
// MARK: - Actions
#objc func panGestureRecognizerAction(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: view)
// Not allowing the user to drag the view upward
guard translation.y >= 0 else { return }
// setting x as 0 because we don't want users to move the frame side ways!! Only want straight up or down in the y-axis
view.frame.origin = CGPoint(x: 0, y: self.pointOrigin!.y + translation.y)
if sender.state == .ended {
let dragVelocity = sender.velocity(in: view)
if dragVelocity.y >= 1300 {
// Velocity fast enough to dismiss the uiview
self.dismiss(animated: true, completion: nil)
} else {
// If the dragging isn’t too fast, resetting the view back to it’s original point
UIView.animate(withDuration: 0.3) {
self.view.frame.origin = self.pointOrigin ?? CGPoint(x: 0, y: 400)
}
}
}
}
#objc func cancelButnPressed() {
dismiss(animated: true, completion: nil)
}
}
3- make the viewController that contain the button that will present your menu conforms to UIViewControllerTransitioningDelegate
extension viewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
BottomMenuPresentationController(presentedViewController: presented, presenting: presenting, topHeightRatio: 0.6, bottomHeightRatio: 0.4)
}
}
4- set the transitioning delegate to self and present your custom presentation Controller
func showBottomMenu() {
let menu = BottomMenuVC()
menu.coordinator = self
menu.modalPresentationStyle = .custom
menu.transitioningDelegate = self
present(menu, animated: true, completion: nil)
}
check this PanGesture Slidable View article

How to present popover from tab bar item?

I want to present a ViewController as popover in Swift 4, but it presents the Viewcontroller normally, this is the code:
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let favViewController = TrialViewController()
let exhibtionViewController = TrialViewController()
let menuViewController = ttttViewController()
let notificationViewController = TrialViewController()
let profileViewController = ttttViewController()
favViewController.tabBarItem.title = "first"
exhibtionViewController.tabBarItem.title = "second"
menuViewController.tabBarItem.title = "third"
notificationViewController.tabBarItem.title = "forth"
profileViewController.tabBarItem.title = "fifth"
favViewController.tabBarItem.image = UIImage(named:"home25")
exhibtionViewController.tabBarItem.image = UIImage(named: "bag25")
menuViewController.tabBarItem.image = UIImage(named: "main_add_25")
notificationViewController.tabBarItem.image = UIImage(named: "notification25")
profileViewController.tabBarItem.image = UIImage(named: "man_man25")
let tabBarItemWidth = Int(self.tabBar.frame.size.width) / (self.tabBar.items?.count)!
let x = tabBarItemWidth * 3;
let newRect = CGRect(x: x, y: 0, width: tabBarItemWidth, height: Int(self.tabBar.frame.size.height))
print(newRect)
menuViewController.modalPresentationStyle = .popover
menuViewController.view.frame = newRect
menuViewController.preferredContentSize = CGSize(width: 150,height: 150)
if let popoverMenuViewController = menuViewController.popoverPresentationController {
popoverMenuViewController.permittedArrowDirections = .down
popoverMenuViewController.delegate = menuViewController as? UIPopoverPresentationControllerDelegate
popoverMenuViewController.sourceRect = newRect
popoverMenuViewController.sourceView = self.tabBar
present(menuViewController, animated: true, completion: nil)
}
viewControllers = [favViewController, exhibtionViewController, menuViewController, notificationViewController, profileViewController]
}
}
what is the problem with my code?
override func viewDidAppear(_ animated: Bool) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "bbb") as! ttttViewController
vc.modalPresentationStyle = .popover //presentation style
vc.preferredContentSize = CGSize(width: 150,height: 150)
vc.popoverPresentationController?.delegate = self as! UIPopoverPresentationControllerDelegate
vc.popoverPresentationController?.sourceView = view
vc.popoverPresentationController?.sourceRect = self.tabBar.frame
self.present(vc, animated: true, completion: nil)
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
demo is here popover

presenting a view controller using custom transition delegate getting a black screen after completion animation

My goal here is animate a second View controller view from the top of the First view with Transperent background.
I am trying to present a Second View Controller from First View Controller.
Created a UIViewControllerAnimatedTransitioning sub class and code implemented is
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
let finalFrameForVC = transitionContext.finalFrame(for: toViewController)
let containerView = transitionContext.containerView
let bounds = UIScreen.main.bounds
// toViewController.view.frame = CGRectOffset(finalFrameForVC, 0, bounds.size.height)
toViewController.view.frame = finalFrameForVC.offsetBy(dx: 0, dy: -bounds.size.height)
containerView.addSubview(toViewController.view)
UIView.animate(withDuration: 0.5, delay: 0.3, animations: {
toViewController.view.frame = finalFrameForVC
}, completion: {
(value: Bool) in
transitionContext.completeTransition(true)
toViewController.view.backgroundColor = UIColor.clear
})
}
conform the First View Controller to UIViewControllerTransitioningDelegate and implemented the below
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?{
flipPresentAnimationController.originFrame = CGRect(x: 0, y: 0, width: view.frame.width, height: 0)
return flipPresentAnimationController
}
In the button action of the First View Controller Implemented the Presentation logic
let sb = UIStoryboard(name: "Main", bundle: nil)
let controller = sb.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
controller.view.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 0)
completion: nil)
self.navigationController?.pushViewController(controller, animated: true)
Every thing works fine but after completion of the animation I am getting the Black screen instead of the Transparent.
Note: I have a button actions in the Second View Controller, if I delete the line
transitionContext.completeTransition(true)
in Custom class black screen is not getting but Button actions are not calling.

UItableviewcontroller as popup not showing data

I want to show UITableViewController as popup on button press. I am able to show popup using this code
let menuViewController = popTableViewController() //popTableViewController
menuViewController.modalPresentationStyle = .Popover
menuViewController.preferredContentSize = CGSizeMake(320, 132)
//menuViewController.tableView = FrontTable
let popoverMenuViewController = menuViewController.popoverPresentationController
popoverMenuViewController?.permittedArrowDirections = .Any
popoverMenuViewController?.delegate = self
popoverMenuViewController?.sourceView = sender
popoverMenuViewController?.sourceRect = CGRect(
x: 10,
y: 10,
width: 1,
height: 1)
presentViewController(
menuViewController,
animated: true,
completion: nil)
but it is showing blank table view in popup. I have also added the content in tableview class but it not showing any reflection on result.
Try adding below code in your UIViewController
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return .None
}
or you can try below code to create popover
let mpopover = self.storyboard?.instantiateViewControllerWithIdentifier("breedPop") as! breedPopover
mpopover.delegate = self
self.citiesPopover = mpopover
citiesPopover!.modalPresentationStyle = .Popover
citiesPopover!.preferredContentSize = CGSizeMake(UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height/2)
let popoverPresentationViewController = citiesPopover!.popoverPresentationController
popoverPresentationViewController?.permittedArrowDirections = UIPopoverArrowDirection.Any
popoverPresentationViewController?.delegate = self
popoverPresentationViewController?.sourceView = breedTextField
popoverPresentationViewController?.sourceRect = CGRectMake(breedTextField.frame.width, breedTextField.frame.height*3, 0, 0)
presentViewController(citiesPopover!, animated: true, completion: nil)

Custom segue without animation in Swift

I'm trying to make custom segue between two view controllers without any animation. Everything works as I expecting just console giving me following message:
2015-06-16 12:19:01.537 prototyp_2[42453:9048104] Unbalanced calls to
begin/end appearance transitions for .
Here is my code:
class Segue_forward: UIStoryboardSegue {
override func perform() {
// zdroj a destinace do lokálních proměnných
var firstVCView = self.sourceViewController.view as UIView!
var secondVCView = self.destinationViewController.view as UIView!
// rozměry displeje
let screenWidth = UIScreen.mainScreen().bounds.size.width
let screenHeight = UIScreen.mainScreen().bounds.size.height
// initial position of destination view
secondVCView.frame = CGRect(x: 0.0, y: 0.0, width: screenWidth, height: screenHeight)
// protože destination VC není subview okna aplikace tak to musime napravit
let window = UIApplication.sharedApplication().keyWindow
window?.insertSubview(secondVCView, aboveSubview: firstVCView)
// animace
UIView.animateWithDuration(0.0, animations: { () -> Void in
firstVCView.frame = CGRectOffset(firstVCView.frame, 0.0, 0.0)
secondVCView.frame = CGRectOffset(secondVCView.frame, 0.0, 0.0)
}) { (Finished) -> Void in
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController, animated: false, completion: nil)
}
}
}
And here is my action:
#IBAction func nextQuestion(sender: AnyObject) {
self.performSegueWithIdentifier("id_forwardSegue", sender: self)
}
Just do it without animation:
override func perform() {
self.sourceViewController.presentViewController(self.destinationViewController as! UIViewController, animated: false, completion: nil)
}
present the new UIViewController with the parentViewController:
self.sourceViewController.parentViewController?.presentViewController(YOUR_VIEW_CONTROLLER, animated: false, completion: nil)

Resources