I'm trying to add a UIView beneath the UINavigationBar in my UINavigationController.
The view will serve as a placeholder for information messages (for example if we are having issues and content is not getting updated).
Adding the view it self and setting it's constraints is not an issue, but it is overlapping the content of the views that is contained in the navigation controller, which is not want I want. How can I set the content of the contained viewcontroller to respect the space which this new view takes up?
The screenshot is showing my custom (orange) view overlapping the content of the viewController that was pushed on to the navigation controller.
Try Subclassing the UINavigationController and then add your orange view's height constraint to it. and call the function whenever you need it
import UIKit
class CustomNavigationController: UINavigationController{
#IBOutlet weak var topViewHeight: NSLayoutConstraint!
func animateHeight(height: CGFloat){
UIView.animate(withDuration: 0.2) {
self.viewControllers.forEach{ vc in
let v = vc.view.frame
vc.view?.frame = CGRect(x: 0, y: height, width: v.width, height: v.height)
}
self.topViewHeight.constant = height
}
}
}
how to use it?
in your VC where you want to show/hide it:
(self.navigationController as? CustomNavigationController)?.animateHeight(height: 50)
Related
I looked at this question, but it does not help: Interacting with presenting view and UIPresentationController
I am trying to implement a sheet presentation controller, similar to the UISheetPresentationController for iOS 15, except I need it to run on iOS 14 as well. And I am also wanting to make it so that it has a small detent, similar to how it is done in the Maps app.
So I have a custom UIPresentationController class and I don't have much in it yet, but is what I have so far:
- (CGRect)frameOfPresentedViewInContainerView {
[super frameOfPresentedViewInContainerView];
CGRect presentedViewFrame = CGRectZero;
CGRect containerBounds = self.containerView.bounds;
presentedViewFrame.size = CGSizeMake(containerBounds.size.width, floor(containerBounds.size.height * 0.5));
presentedViewFrame.origin = CGPointMake(0, containerBounds.size.height - presentedViewFrame.size.height);
return presentedViewFrame;
}
- (BOOL)shouldPresentInFullscreen {
return NO;
}
- (BOOL)shouldRemovePresentersView {
return NO;
}
And this does work. It does display the view controller at half of the height of the presenting view controller. The problem is that the presenting view is no longer interactive because there is a view that gets added by the presentation controller class apparently.
So my question is how do I get the presenting view to be interactive, where I can scroll it and interact with buttons and the other controls? I want to be able to use a presentation controller to present the view controller.
The following allows you to present a shorter modal view controller while still allowing interaction with the presenting view controller. This doesn't attempt to implement what you get with the newer UISheetPresentationController. This only solves the issue of being able to interact with both view controllers while the shorter second controller is in view.
This approach makes use of a custom UIPresentationController. This avoids the need to deal with custom container views and animating the display of the presented view.
Start with the following custom UIPresentationController class:
import UIKit
class ShortPresentationController: UIPresentationController {
override var shouldPresentInFullscreen: Bool {
// We don't want full screen
return false
}
override var frameOfPresentedViewInContainerView: CGRect {
let size = containerView?.frame.size ?? presentingViewController.view.frame.size
// Since the containerView's frame has been resized already, we just need to return a frame of the same
// size with a 0,0 origin.
return CGRect(origin: .zero, size: size)
}
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
guard let containerView = containerView else { return }
// By default the containerView's frame covers the screen which prevents interacting with the presenting view controller.
// Update the containerView's frame to match the area needed by the presented view controller. This allows
// interection with the presenting view controller even while the presented view controller is in view.
//
// This code assumes we want the presented view controller to use the full width of the presenting view controller
// while honoring the preferredContentSize height. It also assumes we want the bottom of the presented view
// controller to appear at the bottom of the presenting view controller. Adjust as needed.
let containerSize = containerView.bounds.size
let preferredSize = presentedViewController.preferredContentSize
containerView.frame = CGRect(x: 0, y: containerSize.height - preferredSize.height,
width: containerSize.width, height: preferredSize.height)
}
}
In the presenting view controller you need to create and present the short view controller. This is fairly typical code for presenting a modal view controller with the important differences of setting the style to custom and assigning the transitioningDelegate.
FirstViewController.swift:
let vc = SecondViewController()
let nc = UINavigationController(rootViewController: vc)
nc.modalPresentationStyle = .custom
nc.transitioningDelegate = self
present(nc, animated: true)
You need to implement one method of the transition delegate in FirstViewController to return the custom presentation controller:
extension FirstViewController: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return ShortPresentationController(presentedViewController: presented, presenting: presenting)
}
}
And lastly, make sure you set the preferredContentSize property of the second view controller. One typical place is in the viewDidLoad of SecondViewController:
preferredContentSize = CGSize(width: 320, height: 300)
That does not include the navigation controller bars (if any). If you want the presenting view controller to set the final size, including the bars, you could set the preferredContentSize on nc just before presenting it. It depends on who you want to dictate the preferred size.
I am trying to achieve the following navigation bar with two titles and an image:
Large title variant:
Small title variant:
I tried subclassing UINavigationBar and adding subviews to it, but they did not render at all.
I tried setting a titleView in storyboard, however it seemed like the titleView is constrained in its height.
What is the proper way to achieve this custom navigation bar?
I also tried this (and setting the viewController in Storyboard to that class):
class NavViewController: UINavigationController {
var titleView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationBar.topItem?.titleView?.backgroundColor = .gray
titleView.frame = CGRect(x: 0, y: 0, width: 100, height: 300)
self.navigationBar.topItem?.titleView = titleView
}
}
Inside the ViewControllerin viewDidLoad, add self.navigationController?.navigationBar.addSubview(imageView). (no need for subclassing)
There is even support for AutoLayout inside UINavigationbar, which is great for animation.
Design your custom view seprately in a xib file, then set that xib as the titleview for your navigationbar
self.navBar.topItem?.titleView = logoImage
Do this for large title, for the smaller one only populate an image in the titleView.
I've a custom transition between two controller that works close to the iOS mail app, which one stays on top of the other with some implemented scrolling behavior.
If I present a new view controller from the Presented view controller which isn't full screen sized, and then I dismiss this new presented view controller, the previous Presented view controller changes its height and then resizes itself.
I know this might be a little confusing but check the gif example below.
As you can see, If I present this custom image picker and then dismiss it, the view controller which presented it warps to full screen and then resizes to the initial value.
How can I prevent this from happening? I want the ViewController which presents the image picker keeps its height.
After the dismiss you can see the resize happening.
Setting the presenting view controllers size
Since it's a UIViewControllerAnimatedTransitioning I create a custom presentation and the size it's set has it's own identity
class CustomPresentationController: UIPresentationController {
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController!) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
}
override var frameOfPresentedViewInContainerView: CGRect {
let containerBounds = self.containerView?.bounds
let origin = CGPoint(x: 0.0, y: ((containerBounds?.size.height)! * 0.05))
let size = CGSize(width: (containerBounds?.size.width)! , height: ((containerBounds?.size.height)! * 0.95))
// Applies the attributes
let presentedViewFrame: CGRect = CGRect(origin: origin, size: size)
return presentedViewFrame
}
override func containerViewWillLayoutSubviews() {
presentedView?.frame = frameOfPresentedViewInContainerView
}
}
Any hint?
thanks
I think that is where the issue is. You are forcing the frame size which is not working out. You should use something like preferredContentSize.
You can simply add this to the viewDidLoad of your CustomPresentationController.
Alternatively you may also try modalPresentationStyle as "Over Current Context"
You can refer very good examples of how you can keep some part of VC as transparent here
I'm using this Framework to have a moving UINavigationBar. I have the following problem with every View (since every view has a UITableView or UICollectionView - Yes this bug appears with UICollectionView, too)
The bottom of every Screen is missing in the size of the UINavigationBar.
The controller is a subclass of UIViewController.
open class SLPagingViewSwift: UIViewController, UIScrollViewDelegate
The UIViewControllers are created globally:
var settingsVC: UserSettingsVC?
Instantiated within the class that creates the controller:
appDelegate.window = UIWindow(frame: UIScreen.main.bounds)
settings = settingsStb.instantiateViewController(withIdentifier: "UserSettingsVC") as? UserSettingsV
// among other VCs
self.setItems() //sets the images at the navigationbar
let items = [itemsArray]
let controllers = [arrayOfVCs] as [UIViewController]
controller = SLPagingViewSwift(items: items, controllers: controllers, showPageControl: false)
controller.indexSelected = 1
nav = UINavigationController(rootViewController: controller)
appDelegate.window?.rootViewController = nav
appDelegate.window?.backgroundColor = cachedBlack
appDelegate.window?.makeKeyAndVisible()
The example controller is a UITableViewController. Same bug appears in every other UIViewController with a UITableView as well as in one UITableViewController with a UICollectionView.
What am I missing? Help is very appreciated.
What you need to do is create a global constant and set the UIEdgeInsetsMake(), for example:
let collectionViewInset = UIEdgeInsetsMake(0, 0, 44, 0)
44 is the height of the navigation bar, and you need to start it after the navigationBar, so y = 44.0.
After doing that you need to set:
collectionView.contentInset = collectionViewInset
And that's it, sorted!
My App is Navigation based Application for iPad.
First Screen is Home Screen. On Clicking button on Home Screen, it pushes to Map Screen. On the Map Screen, i have left Panel (view controller) over the map view controller which occupies 1/4 of the screen.
Left Panel is a View Controller which has Table View. On clicking cell, it should push new viewcontroller to left panel leaving the map view controller behind.
Push
Home Screen -------- Map Screen
|(Added over map screen) Push
|----- Left Panel (Table View) -------- Detail View
I can't use Split View Controller because there is a navigation in left panel as well as in Home Screen. Some times i need to animate/hide left panel. I can customise left panel.
How to implement this structure. Is it good to use Nested Navigation Controller or is there any library available. My App supports both Portrait and Landscape. I am using Swift.
I'm sorry, I don't know Swift as well. However, I think you have to declare a base layout:
you will have a MainViewController that will include a LeftPanelViewController and a FrontViewController. In the MainViewController nib, you will create the main layout using AutoLayout: add a UIView at the left of the screen and another UIView for the frontpage.
Then, link outlets and you will have the layout done! Then you have only to add/remove subviews to leftPanelView and to FrontView.
Now, I think that the right logic is that MainViewController is the NavigationController, so you have to implement the protocol of LeftPanelViewController and FrontViewController, so Main will know how and when add/remove subviews.
The important things is that no one object have to know the existence of MainViewController to preserve the logic. So you have to notify MainViewController for something, to use delegation pattern or something else as NSNotification (be aware, it could be much weight...)
I hope it will bel helpful. Bye
I would solve this by adding a ContainerView as your Left Panel. As you want to do pushes here, you can make the container view controller a navigation controller with the table view as its root view controller.
You'll still be able to animate/hide the container view just like any other view.
This extension for the UIViewController creates the side panel to the left.
private var temp_view:UIView?
private var temp_width:CGFloat?
private weak var temp_sidebar:UIViewController?
extension UIViewController {
func configureSideBar(StoryBoard:String,SideBarIdentifier:String,View:UIView){
var storyboard = UIStoryboard(name: StoryBoard, bundle: nil)
weak var sidebar = storyboard.instantiateViewControllerWithIdentifier(SideBarIdentifier) as? UIViewController
let width:CGFloat = 250//sidebar!.view.frame.size.width
let height = UIScreen.mainScreen().bounds.height
let frame = CGRectMake(0, 0, width, height)
temp_view = View
temp_width = width
temp_sidebar = sidebar
sidebar!.view.frame = frame
addChildViewController(sidebar!)
view.addSubview(sidebar!.view)
view.sendSubviewToBack(sidebar!.view)
toogleSideBarWithAnimation(0.2,Open:false)
}
func getSidebar() -> UIViewController?{
if let sdbar = temp_sidebar {
return sdbar
}
else {
println("Warning:You have tou configure sidebar first")
return nil
}
}
func toogleSideBarWithAnimation(Duration:NSTimeInterval,Open:Bool) {
if let view = temp_view {
UIView.animateWithDuration(Duration, delay: 0, options: UIViewAnimationOptions.TransitionFlipFromLeft, animations: {
var Frame = view.frame
if !Open{
Frame.origin.x = 0
}
else {
Frame.origin.x = temp_width!
}
view.frame = Frame
}, completion: { finished in
})
}
else {
println("Warning:You have tou configure sidebar first")
}
}
}
Let me explain how to use it.
First create a view controller scene and set the storyboard id. In this examples i used "sidebar"
Then add this instruction to the map view controller to the viewDidLoad like this
override func viewDidLoad() {
super.viewDidLoad()
self.configureSideBar("Main", SideBarIdentifier: "sidebar", View: Subview)
}
I suggest adding a UIView with all your view in it in the map view controller
To open and close it use this instruction
self.toogleSideBarWithAnimation(0.2,Open:OpenSidebar)