Programmatically created UIViewController partially hidden by navigation bar - ios

I have a UIViewController subclass that I create programmatically, without Interface Builder. The class is called ColorController, since it edits a color. When I add it to a popup inside a UINavigationController, its content is hidden under the navigation bar. This did not used to happen when the ColorController was pulled from the IB storyboard file.
Is there some property or method I must override on my ColorController to tell it to adjust its bounds when in a navigation controller?
Right now all I'm doing is creating my root UIView (a ColorPicker) and setting it as self.view in loadView().
class ColorController: UIViewController {
private let colorPicker: ColorPicker
init() {
colorPicker = ColorPicker()
}
override func loadView() {
self.view = colorPicker
}

Don't override loadView, override viewDidLoad and add colorPicker self.view
Use SnapKit
colorPicker.snp.makeConstraints { (make) -> Void in
make.leading.trailing.bottom.equalTo(0)
make.top.equalTo(self.topLayoutGuide.snp.bottom);
}
Use iOS UIKit
colorPicker.translatesAutoresizingMaskIntoConstraints = false
var constraints = [NSLayoutConstraint(item: colorPicker, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1, constant: 0),
NSLayoutConstraint(item: colorPicker, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant: 0),
NSLayoutConstraint(item: colorPicker, attribute: .trailing, relatedBy: .equal, toItem: self.view, attribute: .trailing, multiplier: 1, constant: 0)]
if #available(iOS 11.0, *) {
constraints.append(NSLayoutConstraint(item: colorPicker, attribute: .top, relatedBy: .equal, toItem: self.view.safeAreaLayoutGuide, attribute: .top, multiplier: 1, constant: 0))
} else {
constraints.append(NSLayoutConstraint(item: colorPicker, attribute: .top, relatedBy: .equal, toItem: self.topLayoutGuide, attribute: .bottom, multiplier: 1, constant: 0))
}
self.view.addConstraints(constraints)

Related

SideMenu with AutoLayout

I'am trying to make a side menu and i have some problems with setting it with auto layout.
I have a rootViewController that i add to it the leftMenuVC as childVC then i set the constraints.
class RootVC: UIViewController, NavigationBarDelegate {
var leftMenuVC: UIViewController?
var navigationBar = NavigationBar()
var isMenuCollapsed = true
override func viewDidLoad() {
leftMenuVC = leftVC()
addChildViewController(leftMenuVC!)
view.addSubview(leftMenuVC!.view)
leftMenuVC!.didMove(toParentViewController: self)
}
override func viewDidLayoutSubviews() {
if let v = leftMenuVC?.view {
v.translatesAutoresizingMaskIntoConstraints = false
v.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
v.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
v.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -140).isActive = true
v.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}
func menuButtonClicked(){
}
}
So my question is how to change constraints to hide/show the menu with support of orientations
What I usually do when I want to hide a view outside the screen with constraints is:
1 Set all constraints so that the sideview is visible (in active state)
2 Keep in reference the constraint that stick your sideview on one side (here the left one)
leftAnchor = NSLayoutConstraint(item: v, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
view.addConstraint(leftAnchor)
view.addConstraint(NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: v, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: v, attribute: .width, relatedBy: .equal, toItem: view, attribute: .width, multiplier: 0.6, constant: 0))
3 Set one more constraint so that the view will be hidden. Usually it's something like that. Note that the priority is set to 999 to avoid constraint conflicts.
var hiddingConstraint = NSLayoutConstraint(item: v, attribute: .right, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
hiddingConstraint.priority = 999
view.addConstraint(hiddingConstraint)
4 Animate by activating or not your leftAnchor
UIView.animate(withDuration: 0.3) {
self.leftAnchor.active = false
self.view.layoutIfNeeded()
}
So you should end up with a code like this:
class RootVC: UIViewController, NavigationBarDelegate {
var leftMenuVC: UIViewController?
var navigationBar = NavigationBar()
var isMenuCollapsed = true {
didSet {
UIView.animate(withDuration: 0.3) {
self.leftAnchor?.isActive = self.isMenuCollapsed
self.view.layoutIfNeeded()
}
}
}
var leftAnchor : NSLayoutConstraint?
override func viewDidLoad() {
leftMenuVC = leftVC()
addChildViewController(leftMenuVC!)
view.addSubview(leftMenuVC!.view)
leftMenuVC!.didMove(toParentViewController: self)
}
override func viewDidLayoutSubviews() {
if let v = leftMenuVC?.view {
leftAnchor = NSLayoutConstraint(item: v, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
view.addConstraint(leftAnchor!)
view.addConstraint(NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: v, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: v, attribute: .width, relatedBy: .equal, toItem: view, attribute: .width, multiplier: 0.6, constant: 0))
var hiddingConstraint = NSLayoutConstraint(item: v, attribute: .right, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
hiddingConstraint.priority = 999
view.addConstraint(hiddingConstraint)
}
}
func menuButtonClicked(){
isMenuCollapsed = !isMenuCollapsed
}
}
PS: I won't put the constraints setting in viewDidLayoutSubviews, maybe in viewWillAppear, as you don't have to set them every time the device is being rotated. That's the purpose of constraints
Instead of writing this code by yourself, save yourself the trouble.
Here is MMDrawerController to your rescue. I am using it myself. It's super easy to implement and offers lots of customization options. Hope you find it useful. :-)

Label disappear as auto layout is applied programmatically - iOS Swift

Explanation:
In my ViewController i want to create and adjust a view programmatically depending on a radio button. There is one problem as i create the view and setup the constraint programmatically, one of the labels just disappears and if i continued with that most of the component within the ViewController either disappear and move.
Pictures:
Code:
func serviceHoursRadioButtonAction(button: DLRadioButton) {
if button.currentTitle == "yes" {
serviceHoursView = UIView()
if let myView = serviceHoursView {
myView.backgroundColor = UIColor.lightGrayColor()
view.addSubview(myView)
NSLayoutConstraint(item: myView,
attribute: .Top,
relatedBy: .Equal,
toItem: serviceHoursLabel,
attribute: .Bottom,
multiplier: 1, constant: 10).active = true
NSLayoutConstraint(item: myView,
attribute: .LeftMargin,
relatedBy: .Equal,
toItem: self.view,
attribute: .LeftMargin,
multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: myView,
attribute: .RightMargin,
relatedBy: .Equal,
toItem: self.view,
attribute: .RightMargin,
multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: myView,
attribute: .BottomMargin,
relatedBy: .Equal,
toItem: self.view,
attribute: .BottomMargin,
multiplier: 1, constant: 0).active = true
}
} else {
//do nothing, or remove the created view if any.
}
}
Set views property translateAutoresizingMaskIntoConstraints to false.
Setting to self.view.layoutSubviews() fixed mine

Use autolayout on view added to UIWindow

I'm trying to add a subview to the keyWindow of the app, and position it using autolayout. However, autolayout doesn't seem to work at all, whereas setting a frame does. I want to align my view infoSc to the bottom of the keyWindow using the following code:
let infoSc = InfoScreenView()
infoSc.translatesAutoresizingMaskIntoConstraints = false
let keyWindow = UIApplication.sharedApplication().keyWindow!
keyWindow.addSubview(infoSc)
keyWindow.addConstraint(NSLayoutConstraint(item: infoSc, attribute: .Left, relatedBy: .Equal, toItem: keyWindow, attribute: .Left, multiplier: 1, constant: 0))
keyWindow.addConstraint(NSLayoutConstraint(item: infoSc, attribute: .Right, relatedBy: .Equal, toItem: keyWindow, attribute: .Right, multiplier: 1, constant: 0))
keyWindow.addConstraint(NSLayoutConstraint(item: infoSc, attribute: .Bottom, relatedBy: .Equal, toItem: keyWindow, attribute: .Bottom, multiplier: 1, constant: 0))
infoSc.addConstraint(NSLayoutConstraint(item: infoSc, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 100))
However, it appears to have a frame of CGRectZero using this method. Any ideas how to make this work? Ideally I'd also want to align it to something that's inside self.view too, but that throws an error that self.view is not in the view hierarchy of keyWindow.
If you need to draw on the entire window, here is code to do it (in this example I am in the AppDelegate, so window is the AppDelegate.window property).
func tryToDrawOnTheWindow()
{
if let window = window, view = window.rootViewController?.view
{
print("I have a root view")
let infoSc = InfoScreenView(frame: view.frame)
let count = view.subviews.count
view.insertSubview(infoSc, atIndex: count)
infoSc.translatesAutoresizingMaskIntoConstraints = false
let height = NSLayoutConstraint(item: infoSc, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Height, multiplier: 1, constant: 0)
let width = NSLayoutConstraint(item: infoSc, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0)
let offset = NSLayoutConstraint(item: infoSc, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0)
print([height, width, offset])
view.addConstraints([height, width, offset])
}
else
{
print("No root view on which to draw")
}
}
This will let you draw on top of whatever is in the view hierarchy. In my test app, I added a textfield and a blue rect, and the overlay was orange with 40% opacity. Bear in mind that by default the overlay view will consume all taps in this case.
Place and call a method below, based on David S's answer, where you want, passing your view as variable.
func addToWindow(view : UIView) {
guard let delegate = UIApplication.shared.delegate, let window = delegate.window!, let topView = window.rootViewController?.view else {
print("No root view on which to draw")
return
}
print("I have a root view")
let count = topView.subviews.count
topView.insertSubview(view, at: count)
view.translatesAutoresizingMaskIntoConstraints = false
let height = NSLayoutConstraint(item: view, attribute: .height, relatedBy: .equal, toItem: topView, attribute: .height, multiplier: 1, constant: 0)
let width = NSLayoutConstraint(item: view, attribute: .width, relatedBy: .equal, toItem: topView, attribute: .width, multiplier: 1, constant: 0)
let pinToTop = NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: topView, attribute: .top, multiplier: 1, constant: 0)
print([height, width, pinToTop])
topView.addConstraints([height, width, pinToTop])
}

Adding subview with constraints makes view black

I am trying to add a overlay over for all my viewcontrollers by adding this code to my "BaseViewController". However it result in all ViewControllers turning black and behaving oddly.
override public func viewDidLoad()
{
super.viewDidLoad()
overlayView = UIView()
overlayView.backgroundColor = UIColor.redColor() //For testing
view.addSubviewWithMatchingConstraints(overlayView)
...
}
And in UIView extension:
func addSubviewWithMatchingConstraints(subView: UIView)
{
translatesAutoresizingMaskIntoConstraints = false
addSubview(subView)
addConstraint(NSLayoutConstraint(item: subView, attribute: .Width, relatedBy: .Equal, toItem: self, attribute: .Width, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: subView, attribute: .Height, relatedBy: .Equal, toItem: self, attribute: .Height, multiplier: 1, constant: 0))
addConstraint(NSLayoutConstraint(item: subView, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1.0, constant: 0))
addConstraint(NSLayoutConstraint(item: subView, attribute: .CenterY, relatedBy: .Equal, toItem: self, attribute: .CenterY, multiplier: 1.0, constant: 0))
}
The issue was that I set translatesAutoresizingMaskIntoConstraints on the parent rather than the child.
childView.translatesAutoresizingMaskIntoConstraints = false
fixed it

UITableView top to topLayoutGuide programmatically content inset

Due to my attempt to implement iAds "properly" (i.e., sharing a single instance of ADBannerView), I am creating a UITableView programatically inside a UIViewController and adding it to the view. Below are a couple of snippets from my subclass of UIViewController:
From viewDidLoad
self.tableView = UITableView(frame: self.view.bounds, style: .Grouped)
self.tableView.allowsSelectionDuringEditing = true
self.tableView.registerNib(UINib(nibName: "TableViewCellWithSwitch", bundle: nil), forCellReuseIdentifier: "SliderCellIdentifier")
self.tableView.dataSource = self
self.tableView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(self.tableView)
self.tableViewBottomLayoutConstraint = NSLayoutConstraint(item: self.tableView, attribute: .Bottom, relatedBy: .Equal, toItem: self.bottomLayoutGuide, attribute: .Top, multiplier: 1, constant: 0)
self.view.addConstraints([
NSLayoutConstraint(item: self.tableView, attribute: .Left, relatedBy: .Equal, toItem: self.view, attribute: .Left, multiplier: 1, constant: 0),
//NSLayoutConstraint(item: self.tableView, attribute: .Top, relatedBy: .Equal, toItem: self.view, attribute: .Top, multiplier: 1, constant: 0),
//NSLayoutConstraint(item: self.tableView, attribute: .Top, relatedBy: .Equal, toItem: self.view, attribute: .Top, multiplier: 1, constant: self.topLayoutGuide.length),
//NSLayoutConstraint(item: self.tableView, attribute: .Top, relatedBy: .Equal, toItem: self.topLayoutGuide, attribute: .Bottom, multiplier: 1, constant: 0),
NSLayoutConstraint(item: self.tableView, attribute: .Right, relatedBy: .Equal, toItem: self.view, attribute: .Right, multiplier: 1, constant: 0),
self.tableViewBottomLayoutConstraint
])
// This must be called or the use of self.topLayoutGuide will not function
// See: https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewController_Class/index.html#//apple_ref/occ/instp/UIViewController/topLayoutGuide
self.view.layoutSubviews()
iAds implementation (added to try and justify, rightly or wrongly, my implementation of the UITableView)
func showiAds(animated: Bool) {
println("Show iAd")
if !self.showingiAd {
println("Showing iAd")
self.showingiAd = true
// Add the banner view below the content before it's then animated in to view
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
let bannerView = delegate.bannerView
self.bannerBottomConstraint = NSLayoutConstraint(item: bannerView, attribute: .Bottom, relatedBy: .Equal, toItem: self.bottomLayoutGuide, attribute: .Top, multiplier: 1, constant: bannerView.frame.size.height)
if (bannerView.superview != self.view) {
bannerView.removeFromSuperview()
}
self.view.addSubview(bannerView)
self.view.addConstraints([
self.bannerBottomConstraint,
NSLayoutConstraint(item: bannerView, attribute: .Left, relatedBy: .Equal, toItem: self.view, attribute: .Left, multiplier: 1, constant: 0),
NSLayoutConstraint(item: bannerView, attribute: .Right, relatedBy: .Equal, toItem: self.view, attribute: .Right, multiplier: 1, constant: 0),
])
self.view.layoutIfNeeded()
// Only the changing of the value of the top of the banner is animated so it "slides in" from the bottom
self.bannerBottomConstraint.constant = 0
self.view.setNeedsUpdateConstraints()
UIView.animateWithDuration(animated ? 0.5 : 0, animations: { () -> Void in
// Calling layoutIfNeeded here will animate the layout constraint cosntant change made above
self.view.layoutIfNeeded()
}, completion: { (completed) -> Void in
if completed {
println("Completed animation")
}
})
}
}
func hideiAds() {
println("Hide iAd")
if self.self.showingiAd {
self.showingiAd = false
println("Hiding iAd")
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
let bannerView = delegate.bannerView
if bannerView.superview == self.view {
bannerView.removeFromSuperview()
}
self.view.removeConstraint(self.tableViewBottomLayoutConstraint)
self.tableViewBottomLayoutConstraint = NSLayoutConstraint(item: self.tableView, attribute: .Bottom, relatedBy: .Equal, toItem: self.bottomLayoutGuide, attribute: .Top, multiplier: 1, constant: 0)
self.view.addConstraint(self.tableViewBottomLayoutConstraint)
}
}
As you can see, there are 3 constraints commented out. Each of these seem to have different results. I won't post screenshots of them (unless requested), but I will describe them.
NSLayoutConstraint(item: self.tableView, attribute: .Top, relatedBy: .Equal, toItem: self.view, attribute: .Top, multiplier: 1, constant: 0)
iOS 7: Top of table and content of table is at the top of screen. Content is behind the navigation bar
iOS 8: Top of table and content of table is below the navigation bar. Content is below the navigation bar (correct)
NSLayoutConstraint(item: self.tableView, attribute: .Top, relatedBy: .Equal, toItem: self.view, attribute: .Top, multiplier: 1, constant: self.topLayoutGuide.length)
iOS 7: Top of table and content of table is at the top of screen. Content is behind the navigation bar
iOS 8: Top of table and content of table is below the navigation bar. Content is below the navigation bar (correct)
NSLayoutConstraint(item: self.tableView, attribute: .Top, relatedBy: .Equal, toItem: self.topLayoutGuide, attribute: .Bottom, multiplier: 1, constant: 0)
iOS 7: Top of table and content of table is below the navigation bar. Content is below the navigation bar (correct)
iOS 8: Top of table is at the bottom of the navigation bar (correct), but content of table is below the navigation bar, plus (what looks like) the height of the offset again (incorrect)
I am aware that I could just do an if iOS7 {...} else {...}, but that feels pretty dirty and I've got a feeling it's my lack of understanding that's causing this issue, so I'd like to figure out how to have this work on iOS 7 and 8 without having resort to version checking, if possible.
I ended up finding a way of doing this. I'm not sure just how hacky it is, but it's worked on iOS 7.0-8.2 so far.
override func viewDidLayoutSubviews() {
if self.tableView != nil {
// Setting both of these to 0 seems to fix some auto layout issues that crop up in iOS 7/8 depending on
// which item the layout is to, e.g., in iOS 8, having the UITableView's top be to the topLayoutGuide's bottom will
// cause a gap at the top of the UITableView, but this removes that gap and doesn't seem to affect iOS 7
self.tableView.contentInset = UIEdgeInsetsZero
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero
if self.showingiAd {
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
if let bannerView = delegate.bannerView {
let bannerViewHeight = bannerView.frame.size.height
self.tableViewBottomLayoutConstraint.constant = -bannerViewHeight
}
}
}
}

Resources