frame of navigationitem titleView in viewcontroller view - ios

How do I get the frame of a navigationItem's titleView in the coordinate system of the viewcontroller's view?
if let navBarHeight = navigationController?.navigationBar.frame.height,
let navBarWidth = navigationController?.navigationBar.frame.width {
myCustomTitleView.frame = CGRect(x: 0, y: 0, width: navBarWidth, height: navBarHeight)
navigationItem.titleView = myCustomTitleView
}
However, when I check myCustomTitleView's frame origin, I get (0, 0).
I then tried to translate this origin to the viewcontroller's view. what I got was (0,-44), which accounts for the navigation bar height but not for the x-offset.
let originInVCView = view.convert(myCustomTitleView.frame.origin, from: myCustomTitleView)
This can't be right as the titleView obviously has an offset (space for the back button).
How do I correctly extract the translated titleView origin?

You want to make sure you have set the navigation item in viewDidLoad() first. Otherwise it will be nil.
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(image: UIImage(named: "MY_IMAGE"))
navigationItem.titleView = imageView
When done you can get the frame in the VC's viewDidAppear where the view has been laid out:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let nItemFrame = navigationItem.titleView?.frame //<<<---
}

Related

systemLayoutSizeFitting not returning proper size of view

I'm setting a view controller's view as my UITableView's header.
var headerView = CommunityPostDetailTableHeaderViewController()
override func viewDidLoad() {
// other stuffs
headerView.view.frame = CGRect(x: 0, y: 0, width: self.tableView.frame.size.width, height: 100)
headerView.delegate = self
self.tableView.tableHeaderView = headerView.view
}
And using this bit of code to resize it according to the size of view.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let headerView = tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(CGSize(width: tableView.bounds.width, height: 0)).height
var headerFrame = headerView.frame
// Comparison necessary to avoid infinite loop
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
tableView.tableHeaderView = headerView
}
}
}
I'm using this technique for two of my table views.
tableview is presented directly from a view controller like so :
let playerController = VideoDetailController()
playerController.modalPresentationStyle = .fullScreen
playerController.video = video
self.present(playerController, animated: true, completion: nil)
Working fine in all iOS devices.
the other is presented by embedding inside a navigation controller :
let communityPostDetailVC = CommunityPostDetailViewController()
communityPostDetailVC.delegate = self
if let indexpath = indexpath {
communityPostDetailVC.communityPost = datasource[indexpath.row]
communityPostDetailVC.indexpath = indexpath
}
let navigationController = UINavigationController(rootViewController: communityPostDetailVC)
navigationController.modalPresentationStyle = .fullScreen
self.present(navigationController, animated: true)
Not resizing properly on iPhone 5/5s/SE(1st gen)/6/6s/7/8/SE(2nd gen).
I can't figure out why it is not working on smaller phones. You can see blank space in the comparison attachment below. In smaller SE the space is even more.
Any suggestions/ideas are welcome. I'm clueless at this point.
PS: I've nested view controller's. the headerView is a view controller holding another view controller's view. The FB logo and the pink label underneath is part of the nested view controller. Other than that everything else is in headerView's view controller's view.
you are using fullscreen , but you image is a fixed size image . pink colour view also fixed size.
maybe its making a problem .
its actually not an answer. a suggestion for you. you may review again your code. hope you will find .
happy coding

iOS 13 remove UIView for simulate status bar background

I'm working with a UITableViewController which when scrolling makes the navigationBar disappear. Now when the navigation bar is hidden when the user swipes the table view the contents of the cells are seen below the status bar ...
To solve this problem I tried to insert a UIView to simulate a background of the status bar and everything works but the problem is that when I close the UITableViewController the background view of the status bar is not removed from the superview
For now my code is this, can you help me understand where I am wrong? why can't I remove the UIView from the superview?
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupStatusBarView()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.isHidden = true
UIApplication.shared.windows.first?.viewWithTag(1)?.removeFromSuperview()
}
//MARK: - Setup Status Bar View
func setupStatusBarView() {
let height = view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
let statusBarView = UIView()
statusBarView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height:height+5)
statusBarView.backgroundColor = .systemBackground
statusBarView.tag = 1
UIApplication.shared.windows.first?.addSubview(statusBarView)
}
viewDidLayoutSubviews get calls multiple times and you have put setupStatusBarView() in viewDidLayoutSubviews that means your background view has been added multiple times and this is totally wrong flow!
You are removing topmost view only not previous ones!
You should set frame in viewDidLayoutSubviews and should add the view from viewDidLoad!
try this one
let subviewArray = UIApplication.shared.windows.first?.subviews
for view in subviewArray!{
if view.tag == 1{
view.removeFromSuperview()
}
}

Custom TabBar layout for UITabBarViewController

Please refer to this Answer.
I am trying to do the same thing, however I want to do this in a Tab Bar App where the Now Playing bar is above the Tab Bar in all the scenes of the app.
Update:
I want to have a view at the bottom of the screen (above the tab bar) and under the content views of the different tabs (not above them). In addition, I want to have the ability to remove this view at a certain point making the main view take the whole screen.
I can do this using the mentioned Answer by changing the constraints of the nowPlaying view programmatically.
Using UITabBarViewController subclass it is possible:
Ex:
class DashBoardViewController: UITabBarController {
let nowPlayingBar:UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = .blue
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
initView()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
nowPlayingBar.frame = tabBar.frame
}
override func viewDidAppear(_ animated: Bool) {
var newSafeArea = UIEdgeInsets()
// Adjust the safe area to accommodate
// the height of the bottom views.
newSafeArea.bottom += nowPlayingBar.bounds.size.height
// Adjust the safe area insets of the
// embedded child view controller.
self.childViewControllers.forEach({$0.additionalSafeAreaInsets = newSafeArea})
}
private func initView() {
nowPlayingBar.frame = tabBar.frame
view.addSubview(nowPlayingBar)
}
}
You'll add your view/container to your app window, you'd do something like
guard let window = (UIApplication.shared.delegate as? AppDelegate)?.window
else { return } // check if there's a window
let containerHeight: CGFloat = 50 // height for the view where you wish to add the music player
let containerFrame = CGRect(x:0, y: window.frame.maxY - (tabBar.frame.height + containerHeight), width: window.frame.width, height: containerHeight)
// most important part here is the y axis in some sense, you will add the height of the tabBar and the container, then subtract it from window.frame.maxY
let container = UIView(frame: containerFrame)
// now you have the container do whatever you want with it
window.addSubView(container) // finally add the container to window as a subview

Make NavigationBar's titleView larger than itself

I want to place an image in the middle of a navigation bar that is bigger then the bar itself. So far I tried to use a UIView with a UIImageView inside and it works quite well as you can see here:
However as soon as I push another controller and pop back my ImageView gets cropped to the size of the NavigationBar again.
Any ideas on how to prevent the cropping?
My code so far for iOS 11:
override func viewDidLoad() {
super.viewDidLoad()
let logo = UIImage(named: "Logo")
let titleView = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 60))
let imageView = UIImageView(image: logo)
imageView.frame = CGRect(x: 0, y: 0, width: titleView.frame.width, height: titleView.frame.height)
titleView.addSubview(imageView)
imageView.contentMode = .scaleAspectFit
imageView.image = logo
navigationItem.titleView = titleView
}
Edit: Currently there is a temporary solution which uses an observer to overwrite the clipsToBounds property of the view that causes the trouble: Link (shout out to #trungduc for that)
I found why you got this issue. It's because of a private view which has name _UINavigationBarContentView. It's a subview of UINavigationBar. navigationItem.titleView is contained in this view.
At first time, when you change navigationItem.titleView. _UINavigationBarContentView.clipsToBounds is false .But after you push another controller and pop back, _UINavigationBarContentView.clipsToBounds is true. That's why titleView is cropped.
So i have a temporary solution. Each time when viewController appears, find this view and change _UINavigationBarContentView.clipsToBounds to false and layout titleView.
override func viewDidAppear(_ animated: Bool) {
for view : UIView in (navigationController?.navigationBar.subviews)! {
view.clipsToBounds = false;
}
navigationItem.titleView?.layoutIfNeeded()
}
override func viewWillAppear(_ animated: Bool) {
for view : UIView in (navigationController?.navigationBar.subviews)! {
view.clipsToBounds = false;
}
navigationItem.titleView?.layoutIfNeeded()
}
I tried and it works. But i think you shouldn't do something whit it because it's private view. Maybe Apple don't want us do anything with it.
Hope somehow my suggestion can help you. Good luck ;)
SOLUTION
Adding observer for _UINavigationBarContentView.clipsToBounds, each time when it changes to false, set to true and update layout of titleView
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let logo = UIImage(named: "Logo")
let titleView = UIView(frame: CGRect(x: 0, y: 0, width: 60, height: 60))
let imageView = UIImageView(image: logo)
imageView.frame = CGRect(x: 0, y: 0, width: titleView.frame.width, height: titleView.frame.height)
titleView.addSubview(imageView)
imageView.contentMode = .scaleAspectFit
imageView.image = logo
navigationItem.titleView = titleView
navigationController?.navigationBar.subviews[2].addObserver(self, forKeyPath: "clipsToBounds", options: [.old, .new], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if (navigationController?.navigationBar.subviews[2].isEqual(object))! {
DispatchQueue.main.async {
self.navigationController?.navigationBar.subviews[2].clipsToBounds = false
self.navigationItem.titleView?.layoutIfNeeded()
}
}
}
deinit {
navigationController?.navigationBar.subviews[2].removeObserver(self, forKeyPath: "clipsToBounds")
}
For more detail and easier, you can check my demo here https://github.com/trungducc/stackoverflow/tree/big-title-navigation-bar
You could try to put the code in the viewWillAppear method. This way you will add the image to the bar everytime the view appears. However you should then remove the inageview within the viewDidDissappear method. If you need it in several views you could subclass the UIViewController and use this one.
The UINavigationBar has UIBarMetrics if you want a custom height but it's not fully interactive.
There's a bunch of code suggested by apple to actually develop nice things like iMessage app.

iOS Navigation Item issues of extra padding top on landscape mode

There is extra padding top when my app in landscape.
Here is my code when create navigation bar programmatically.
Any advice to remove the padding top when in landscape mode?
let navigationBar = UINavigationBar(frame: CGRectMake(0, 0, self.view.frame.size.width, 44)
navigationBar.backgroundColor = UIColor.redColor()
navigationBar.delegate = self;
let navigationItem = UINavigationItem()
navigationItem.title = "Title"
let btnLeft = UIButton(frame: CGRectMake(0, 0, 44, 44))
btnLeft.setImage(UIImage(named: “myImage.png"), forState: .Normal)
let leftButton = UIBarButtonItem()
leftButton.customView = btnLeft
navigationItem.leftBarButtonItem = leftButton
navigationBar.items = [navigationItem]
self.view.addSubview(navigationBar)
Snapshot from simulator (Landscape)
Snapshot from simulator (Portrait)
You should easily solve with this setting (in your viewDidLoad for example):
self.edgesForExtendedLayout = UIRectEdge.None
Alternative: You can put it also in your UINavigationController delegate method (if you have setted in your class your navigation delegate UINavigationControllerDelegate ) called willShowViewController:
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
viewController.edgesForExtendedLayout = UIRectEdge.None
}
If you work with UITableView you may also to do:
self.automaticallyAdjustsScrollViewInsets = false
You can also edit directly to the attribute inspector:
If anyone of these approaches don't work, by default your UINavigationBar have different heights based on your orientation. For example, the navigation bar is 44 points in portrait and 32 points in landscape.
A workaround can be:
// Create an IBOutlet of your navigationBar height constraint
#IBOutlet weak var navBarHeight: NSLayoutConstraint
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if self.view.bounds.size.height > self.view.bounds.size.width {
self.navBarHeight.constant = 44
} else {
self.navBarHeight.constant = 32
}
}

Resources