Customized navigation bar does not receive touch events in entire view - ios

I am trying to implement this image of my implementation.
Where the three buttons below the uilabel are clickable. I have one UIView as a subview of my custom navigation bar view, then two views within that view, one is the uilabel and the second is the uiview of uibuttons i
I've tried implementing solutions from other answers like
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.point(inside: point, with: event) {
self.isUserInteractionEnabled = true
}else{
self.isUserInteractionEnabled = false
}
return super.hitTest(point, with: event)
}
But that didn't work. I noticed that if I tapped above the button near the position of the default uinavigation bar size then the tap is recognized.
The size of my navigation bar is CGSize(width: self.frame.size.width, height: 100) if that helps.
Update
just adding my custom navigation class and it's usage
class CustomNavigationBar: UINavigationBar {
override func sizeThatFits(_ size: CGSize) -> CGSize {
let newSize :CGSize = CGSize(width: self.frame.size.width, height: 100)
return newSize
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.point(inside: point, with: event) {
self.isUserInteractionEnabled = true
}else{
self.isUserInteractionEnabled = false
}
return super.hitTest(point, with: event)
}
}
let navigationController = UINavigationController(navigationBarClass: CustomNavigationBar.self, toolbarClass: nil)
navigationController.setViewControllers([mainController], animated: false)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()

Please check that your navigation bar height was increased or not, that will be the reason stoping complete interaction of Navigation bar, Add following code and check will work fine,
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let height: CGFloat = 100 //whatever height you want
let bounds = self.navigationController!.navigationBar.bounds
self.navigationController?.navigationBar.frame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height + height)
}

Hi you should change the navigationbar frame in viewdidappear only otherwise frame will not effect.
self.navigationController?.navigationBar.frame = CGRect(x: 0, y: 0, width:width, height: height)

Found the problem!
The view, which contained the title and buttons, was set with a frame with a height of 60 instead of the height of my custom navigation bar. Thanks for the help guys.
Lesson here was
After setting custom height for navigation bar make sure it's subView's bounds matches the navigation bar's bounds.

Related

How can I fill image in scroll view to fill screen?

I am playing around with scroll views, and I've run into an issue I'be stuck with. I have a view controller create in Storyboard. The view controller contains a scroll view which fills the entire superview.
I then added the images programmatically to the scroll view. The images do show within the scroll view and paging works just fine. Only problem is the scroll view is set ti fill superview but the image view that hold the images seems like it stops above where the navigation bar would be. How can I have the image view fill the whole view within the scroll view?
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pagingView: UIPageControl!
var images = [UIImage]()
var frame = CGRect(x: 0, y: 0,width: 0,height: 0)
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
scrollView.isPagingEnabled = true
images = [UIImage(named: "Slide1")!, UIImage(named: "Slide2")!, UIImage(named: "Slide3")!, UIImage(named: "Slide4")!]
pagingView.numberOfPages = images.count
// This is where I think I'm having the height problem.
for i in 0..<images.count {
let imageView = UIImageView()
let x = self.view.frame.size.width * CGFloat(i)
imageView.frame = CGRect(x: x, y: 0, width: self.view.frame.width, height: self.view.frame.height)
imageView.contentMode = .scaleAspectFill
imageView.image = images[i]
scrollView.contentSize.width = scrollView.frame.size.width * CGFloat(i + 1)
scrollView.addSubview(imageView)
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
pagingView.currentPage = Int(pageNumber)
}
After setting nav bar to hidden, here is the output
Scroll view background color is red
In this case you need to enable the parent view clipsToBounds. Set UIScrollview clipsToBounds property to True.
Programmatically scrollView.clipsToBounds = true
In UIStoryBoard - Click the view->Attributes Inspector
If you would like to see the whole screen, make sure to add the topConstraint of scrollView assigned superView and hide the navigationBar in viewWillAppear,
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
Make sure to remove the status bar by
override var prefersStatusBarHidden: Bool {
return true
}
Update the Y position of Image.
imageView.frame = CGRect(x: x, y: **self.scrollView.frame.minY**, width: self.view.frame.width, height: self.view.frame.height)
Update the scrollView topConstraint by -20.

How to Increase Navigation Bar Height

1. How can I increase the hight of my navigation bar just like Apple did for the iMessage app:
2. How do they make the navigation bar expand when the titleView is clicked to look like this:
I've tried creating a larger titleView, but it just gets clipped to the bounds of the default navigation bar height. How are they able to achieve this? Also, my view controllers are embedded in a navigation controller programmatically.
Unsure if you've just embedded Navigation into the app... or set this up programatically.
You'll need to add your navBar as a sort of "IBOulet", and then modify it's height based on an function/action/etc.
Say when you click "name button"; then something akin to "mainNavBar.heightAnchor.constant = 400" (where 400 is the new height).
Or if you've set this up manually, and have a auto layout constraint on the height of your nav bar, then you could set that up as a IBOutlet; and access it easier.
It would help if you posted some of your code, or atleast how you've set this up.
Use this class for navigation bar height :
import Foundation
import UIKit
class NavigationBar: UINavigationBar {
//set NavigationBar's height
//For iphonex, I recommended to set the minimum height to 88 or higher.
var customHeight : CGFloat = 100
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
translatesAutoresizingMaskIntoConstraints = false
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: customHeight)
}
override func layoutSubviews() {
super.layoutSubviews()
self.tintColor = .black
frame = CGRect(x: frame.origin.x, y: 0, width: frame.size.width, height: customHeight)
// title position (statusbar height / 2)
setTitleVerticalPositionAdjustment(-10, for: UIBarMetrics.default)
for subview in self.subviews {
var stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("BarBackground") {
subview.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: customHeight)
subview.backgroundColor = .yellow
}
stringFromClass = NSStringFromClass(subview.classForCoder)
if stringFromClass.contains("BarContent") {
subview.frame = CGRect(x: subview.frame.origin.x, y: 20, width: subview.frame.width, height: customHeight - 20)
}
}
}
}
Use navigation view instead of navigation bar or use custom view on viewDidLoad function hide navigation bar and show your custom view

UIButton subview is offset in subclass

I have a UIButton subclass intended to show a selected state of a button. The selected state simply places a thick black line at the bottom of the button view and when unselected it hides the black line. However, when using this in a UIButton subclass, the black line view is offset. I have tried playing around with insets, but I don't think that is the problem. Here is my subclass:
class TabButton: UIButton {
private var height:CGFloat = 5
private var selectedIndicator:UIView?
override var isSelected: Bool {
didSet {
selectedIndicator?.isHidden = !isSelected
}
}
fileprivate func initializeSelector(_ frame: CGRect) {
selectedIndicator = UIView(frame: CGRect(x: 0, y: frame.size.height - height, width: frame.size.width, height: height))
selectedIndicator?.backgroundColor = UIColor.black
self.addSubview(selectedIndicator!)
}
override func awakeFromNib() {
super.awakeFromNib()
initializeSelector(self.frame)
}
}
The desired button should look like this:
But instead it looks like this:
Can anyone help me understand what is happening here and how to fix it? Thanks!
Try this, in layoutSubviews you get the final frame:
override layoutSubviews() {
super.layoutSubviews()
selectedIndicator.frame = CGRect(x: 0, y: frame.size.height - height, width: frame.size.width, height: height)
}
The frame of the selectedIndicator is set only once when initializeSelector is called. When the button changes its frame, it does not change the frame of subviews, you need to manually update the frame of selectedIndicator.
To do so, you need to override layoutSubviews() method of UIView.
override layoutSubviews() {
super.layoutSubviews()
selectedIndicator?.frame = CGRect(x: 0, y: frame.size.height - height, width: frame.size.width, height: height)
}
See this answer to know when layoutSubviews() is called.

childViewController.view.frame in UIView present different value

I use childViewController to separate view in project, but some strange issue happened, here is my code.
class ViewController: UIViewController {
var container = UIView()
var childVC = ChildViewController()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
addChildViewController(childVC)
childVC.didMove(toParentViewController: self)
addChildView()
setContainerFrame()
}
func setContainerFrame() {
container.frame = CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height - 100)
container.backgroundColor = .red
view.addSubview(container)
}
func addChildView() {
let frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
childVC.view.frame = frame
childVC.view.backgroundColor = .green
container.addSubview(childVC.view)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("childVC.view.frame: \(childVC.view.frame)")
}
}
class ChildViewController: UIViewController {
}
when I exchange the order invoking func addChildView() and func setContainerFrame() in ViewController's viewDidLoad(), xcode'console prints different frame log.
This is due to the autoresizingMask of the childVC.view. By default this is set to have flexible width and height. So if you set the frame of the container and then the frame of the childVC.view then you get the actual value you set but if you reverse it and set the frame of the childVC.view and then the container the frame of the childVC.view is automatically updated to have the same relative width and height.
For example if the frame size for the container was 100 x 100 and then you change it to 200 x 200 the frame size for childVC.view will be doubled.
To remove this set the childVC.view.autoresizingMask = [] so it remains as you set it.
The strange behavior occurs because in addChildView() function you are adding the child view controller's view as subview to container view but container view's frame is setting in the function setContainerFrame() which is not yet called. That means you are adding a subview to a view whose frame hasn't set already.

SFSafariViewController NOT fullscreen / content presented on top

I'm making a very simple app for a demo and am trying to present a webpage using SFSafariViewController (I need to use SF versus WKWebView so to be able to access cookies).
I would really like to present the User with some UI buttons, but I've been unable to pull it off.
I tried this snippet (placed in the completion callback of presentViewController():
let width: CGFloat = 66
let x: CGFloat = self.view.frame.width - width
// It can be any overlay. May be your logo image here inside an imageView.
let overlay = UIView(frame: CGRect(x: x, y: 20, width: width, height: 44))
overlay.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.5)
svc.view.addSubview(overlay)
... outlined in this post. In their case, they're attempting to cover the reload button with a small view. Regardless of the use-case, for me the view immediately disappears when I load SFSafariViewController (I can see it for a moment and it disappears).
I was thinking about presenting the button in an .OverContext modal, but then the User would be unable to interact with the SFSafariViewController, which also doesn't work.
Here's essentially what I'm after (pardon the gross, quick mockup) ... basically, SafariViewController with a view presented over it (see bottom) ... the transparency is just to show that it's being presented over Safari).
Any recommendations are greatly appreciated.
Figured it out ... there's likely some slight race condition going on that was preventing the recommended "draw a rectangle" code from working as desired. What I did:
Subclassed SFSafariWebViewController
In viewDidAppear, implemented a slight delay using NSTimer that draws any additional view elements
This also ended up helping me hide the status bar, which was giving me issues (see mockup).
Here's the relevant code:
import UIKit
import SafariServices
class MySafariVC: SFSafariViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
var frame = self.view.frame
let OffsetY: CGFloat = 44
frame.origin = CGPoint(x: frame.origin.x, y: frame.origin.y - OffsetY)
frame.size = CGSize(width: frame.width, height: frame.height + (1 * OffsetY))
self.view.frame = frame
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "drawNavBar", userInfo: nil, repeats: false)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("i laid out my subviews")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prefersStatusBarHidden() -> Bool {
return true
}
func drawNavBar() {
let height: CGFloat = 44
let y: CGFloat = self.view.frame.height - height
// It can be any overlay. May be your logo image here inside an imageView.
let overlay = UIView(frame: CGRect(x: 0, y: y, width: self.view.frame.width, height: height))
overlay.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.9)
self.view.addSubview(overlay)
}
}

Resources