How can i write the following code using NSLayoutConstraints (or anchors) in a UIView subclass?
func commonInit() {
aView = UIView()
aView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(aView)
}
override func layoutSubviews() {
super.layoutSubviews()
let smallerSide = min(bounds.width, bounds.height)
aView.bounds = CGRect(x: 0.0, y: 0.0, width: smallerSide, height: smallerSide)
aView.center = CGPoint(x: bounds.midX, y: bounds.midY)
}
One objective is to avoid using layoutSubviews().
Also, the aView must maintain a 1:1 aspect ratio.
If you'd need any more info, please let me know.
PS: Please let's use swift3, thanks.
Check the below code:
let view = UIView(frame: CGRectZero)
view.setTranslatesAutoresizingMaskIntoConstraints(false)
super.init(frame: frame)
let viewsDict = ["view": view]
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[view]-0-|", options: .allZeros, metrics: nil, views: viewsDict))
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[view]-0-|", options: .allZeros, metrics: nil, views: viewsDict))
addSubview(view)
I have replicated the same behaviour via size classes, but only for iPhones. Check this storyboard. (Its not code, what you asked for)
Storyboard
Since iPads have width:Regaular and Height:Regular, it seems it is not possible for iPads, without using layoutSubview.
Related
I'm trying to draw a a line at the top of a table view as follows:
func addTopLineToTableView() {
let layer = CALayer()
layer.backgroundColor = UIColor.green.cgColor
layer.frame = CGRect(x: tableView.frame.origin.x, y: tableView.frame.origin.y, width: tableView.frame.size.width, height: 1.0)
view.layer.addSublayer(layer)
}
This works perfectly on every device except an iPhone X.
I've tried getting the safeAreaInsets and the safeAreaLayoutGuide but that didn't seem to do anything.
Any help greatly appreciated!
EDIT
Above is what the end result should look like. Here the table view has a red background and my green CALayer is right at the top as expected. It has been placed there using the function above. This is the way it looks on every device except the iPhone X.
Here is what happens when the same code is run on an iPhone X.
Like friends said before, the line can be set to the parts, you can extend the view
extension UIView {
#IBInspectable var topBorderWidth: CGFloat {
get {
return 0.0 // Just to satisfy property
}
set {
let line = UIView(frame: CGRect(x: 0.0, y: 0.0, width: bounds.width, height: newValue))
line.translatesAutoresizingMaskIntoConstraints = false
line.backgroundColor = UIColor.green
self.addSubview(line)
let views = ["line": line]
let metrics = ["lineWidth": newValue]
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|[line]|", options: [], metrics: nil, views: views))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[line(==lineWidth)]", options: [], metrics: metrics, views: views))
}
}
}
Such use
testView.topBorderWidth = 1
I have a view, say a red rectangle, and I have a subview of it, a green rectangle, all added programmatically.
Now, how can I make a green rectangle fill the whole red rectangle in width and height without getting current size of the screen, so that when red rectangle resizes on screen rotation, the green one resizes automatically too?
let redView = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 350))
redView.backgroundColor = UIColor.redColor()
redView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
view.addSubview(redView)
let greenView = UIView(frame: redView.bounds)
greenView.backgroundColor = UIColor.greenColor()
redView.addSubview(greenView)
greenView.autoresizingMask = redView.autoresizingMask
class RedRect:UIView {
void layoutSubviews(){
greenRect.frame = bounds
}
}
should be frame = bounds
You can use Visual Format Auto Layout like below where self == YOUR_GREEN_RECT
guard let superview = self.superview else {
return
}
self.translatesAutoresizingMaskIntoConstraints = false
superview.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[subview]-0-|", options: .DirectionLeadingToTrailing, metrics: nil, views: ["subview": self]))
superview.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[subview]-0-|", options: .DirectionLeadingToTrailing, metrics: nil, views: ["subview": self]))
Or just using Autoresizingmask
self.autoresizingMask = [.flexibleWidth, .flexibleHeight]
You can override the layoutSubviews() method of Red Rectangle. Inside the layout, just reassign the red rectangle frame to the green Rectangle frame. This would make the frame resize and refill on orientation as well.
This is merely an example code for reference.
class RedRect:UIView {
void layoutSubviews(){
greenRect.frame = bounds
}
}
I made a a custom keyboard toolBar:
To do that I created a toolbar
let keyboardToolbar = UIToolbar(frame: CGRectMake(0, 0, self.view.bounds.size.width, 44))
and a view for the banner
adToolbar = GADBannerView(frame: CGRectMake(0, 44, self.view.bounds.size.width, 44))
then I grouped them in another UIToolbar (I tried UIView too)
let clusterView = UIToolbar(frame: CGRectMake(0, 0, self.view.bounds.size.width, 88))
clusterView.addSubview(adToolbar)
clusterView.addSubview(keyboardToolbar)
and I added the view to the UITextField's keyboard.
Everything ok, but when I rotate the device happens this:
(clusterView UIToolbar resize correctly but not the two contained bars...)
I tried with
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
methodWhichGeneratesToolbar()
}
But it's the same, what can I try to solve this issue?
P.S.: I've made an example project.
Here's a suggestion on how to do it with programmatic autolayout using Visual Format Language (VFL). You'll need to have a look at the VFL docs to understand the VFL string syntax (They are pinning the outer view to the top and sides of the main view, and pinning the two subviews inside and to each other, and setting their height to 44).
I don't have AdMob installed, so I used a regular UIView instead of the banner view, but hopefully it should resize similarly - this code works ok on the 9.2 simulator in a test app
let keyboardToolbar = UIToolbar(frame: CGRectMake(0, 0, self.view.bounds.size.width, 44))
keyboardToolbar.translatesAutoresizingMaskIntoConstraints = false //This is critical for all programmatic autolayout - if you forget it nothing will work
let adToolbar = UIView(frame: CGRectMake(0, 44, self.view.bounds.size.width, 44))
adToolbar.translatesAutoresizingMaskIntoConstraints = false
let clusterView = UIToolbar(frame: CGRectMake(0, 0, self.view.bounds.size.width, 88))
clusterView.translatesAutoresizingMaskIntoConstraints = false
//Map views to keys used in visual format language strings
let views = ["keyboardToolbar":keyboardToolbar,"adToolbar":adToolbar,"clusterView":clusterView]
//Map values to strings used in vfl strings
let metrics = ["barHeight":44]
//In named variables to make it clear what they are
//Syntax is explained in link above
let verticalConstraintsStr = "V:|[keyboardToolbar(barHeight)][adToolbar(barHeight)]|"
let adHorizontalConstraintsStr = "|[adToolbar]|"
let keyboardHorizontalConstraintsStr = "|[keyboardToolbar]|"
let subViewConstraintStrs = [
verticalConstraintsStr,
adHorizontalConstraintsStr,
keyboardHorizontalConstraintsStr
]
//Views must be added to subviews before adding constraints
// if the superview is referenced using
//the | symbol in the VFL strings
clusterView.addSubview(keyboardToolbar)
clusterView.addSubview(adToolbar)
//Converts strings to constraints for subviews and add them
for constraintStr in subViewConstraintStrs {
let allConstraints = NSLayoutConstraint.constraintsWithVisualFormat(constraintStr, options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views)
clusterView.addConstraints(allConstraints)
}
let clusterVerticalConstraintsStr = "V:|[clusterView]" //Note no | at the end - no bottom pin
let clusterHorizontalConstraintsStr = "|[clusterView]|"
view.addSubview(clusterView)
//Same process for the enclosing view
for constraintStr in [clusterVerticalConstraintsStr,clusterHorizontalConstraintsStr] {
let allConstraints = NSLayoutConstraint.constraintsWithVisualFormat(constraintStr, options: NSLayoutFormatOptions(rawValue: 0), metrics: metrics, views: views)
view.addConstraints(allConstraints)
}
VFL is powerful but annoying to debug, and can't do all types of constraint (e.g. not alignments - you have to use an even more verbose API for those).
Hello
I'm trying to put an Image as a background for my ViewControllers, guiding myself for other posts I found this way:
I created the following extension:
extension UIView {
func addBackground() {
// screen width and height:
let width = UIScreen.mainScreen().bounds.size.width
let height = UIScreen.mainScreen().bounds.size.height
let imageViewBackground = UIImageView(frame: CGRectMake(0, 0, width, height))
imageViewBackground.image = UIImage(named: "msa_background")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.ScaleAspectFill
imageViewBackground.clipsToBounds = true
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}
}
I'm calling this extension in every ViewController with the following code:
override func viewDidLoad() {
super.viewDidLoad()
self.view.addBackground()
}
The problem is when I rotated the screen the image in the background don't fill all the space in the ViewController, I have checked any possible solution that I found but I can't find a way to do it.
I really appreciate any help from you
You are adding the image with the current frame of the screen and never changing it. When you rotate the device it will keep the same frame.
Change it to use AutoLayout like this...
extension UIView {
func addBackground(imageName: String, contentMode: UIViewContentMode) {
let imageViewBackground = UIImageView()
imageViewBackground.image = UIImage(named: imageName)
// you can change the content mode:
imageViewBackground.contentMode = contentMode
imageViewBackground.clipsToBounds = true
imageViewBackground.translatesAutoresizingMaskIntoConstraints = false
self.insertSubview(imageViewBackground, atIndex: 0)
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[imageViewBackground]|",
options: [],
metrics: nil,
views: ["imageViewBackground", imageViewBackground]))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[imageViewBackground]|",
options: [],
metrics: nil,
views: ["imageViewBackground": imageViewBackground]))
}
}
The auto layout constraints will then make the image view fit to the view no matter how the orientation of the device or frame of the view changes.
I have a scrollview which contains two containers (UIViews). One UIView contains a UITableView which displays correctly and another ViewController. The viewController has an image which fills it completely. However, when adding the View Controller if I set a background color that shows up.
//
// SideBarViewController.swift
// Sidebar
//
// Created by Satyajit Sarangi on 12/10/15.
// Copyright © 2015 Satyajit Sarangi. All rights reserved.
//
import UIKit
class SideBarViewController: UIViewController {
// MARK: Properties
var scrollView: UIScrollView!
let menuTableView = UITableView()
let sideBarContainerView = UIView()
let mainContainerView = UIView()
let testContainerView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Create a dictionary for the views for the visual format language
let containerViewsDict = Dictionary(dictionaryLiteral: ("sidebar_container", sideBarContainerView), ("main_container", mainContainerView))
// let scrollViewDict = Dictionary(dictionaryLiteral: ("scrollview", scrollView))
// Create two container
// Add the scroll view
scrollView = UIScrollView(frame: view.frame)
// Set Properties for scrollview
scrollView.scrollEnabled = true
scrollView.pagingEnabled = true
scrollView.contentSize = CGSizeMake(view.frame.width, view.frame.height)
// sideBarContainerView.backgroundColor = UIColor.yellowColor()
scrollView.addSubview(sideBarContainerView)
scrollView.addSubview(mainContainerView)
setupMainContainerView()
setupSideBarTableView()
// Add the Scroll View
view.addSubview(scrollView)
let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|[sidebar_container]-5-[main_container]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: containerViewsDict)
let scrollview_verticalConstraints1 = NSLayoutConstraint.constraintsWithVisualFormat("V:|[sidebar_container]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: containerViewsDict)
let scrollview_verticalConstraints2 = NSLayoutConstraint.constraintsWithVisualFormat("V:|[main_container]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: containerViewsDict)
sideBarContainerView.translatesAutoresizingMaskIntoConstraints = false
mainContainerView.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addConstraints(horizontalConstraints)
scrollView.addConstraints(scrollview_verticalConstraints1)
scrollView.addConstraints(scrollview_verticalConstraints2)
// Add constraints for the Scroll View
// let scrollview_constraint_v = NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollview]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: scrollViewDict)
// let scrollview_constraint_h = NSLayoutConstraint.constraintsWithVisualFormat("H:|[scrollview]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: scrollViewDict)
// scrollView.addConstraints(scrollview_constraint_v)
// scrollView.addConstraints(scrollview_constraint_h)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
view.frame = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height)
scrollView.setContentOffset(CGPoint(x: 100, y: 0), animated: true)
}
// MARK: Set UI Properties
func setupSideBarTableView() {
menuTableView.frame = CGRectMake(0, 0, 200, scrollView.frame.height)
menuTableView.contentSize = CGSizeMake(1000, 1000)
menuTableView.backgroundColor = UIColor.redColor()
sideBarContainerView.addSubview(menuTableView)
let dict = Dictionary(dictionaryLiteral: ("menu_table", menuTableView))
let constraint_h = NSLayoutConstraint.constraintsWithVisualFormat("H:|[menu_table]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: dict)
let constraint_v = NSLayoutConstraint.constraintsWithVisualFormat("V:|[menu_table]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: dict)
sideBarContainerView.frame = menuTableView.frame
sideBarContainerView.addConstraints(constraint_h)
sideBarContainerView.addConstraints(constraint_v)
}
func setupMainContainerView() {
// testContainerView.frame = CGRectMake(0, 0, 320, scrollView.frame.height)
// testContainerView.contentSize = CGSizeMake(1000, 1000)
// testContainerView.backgroundColor = UIColor.yellowColor()
let testContainerView = TestViewController()
self.addChildViewController(testContainerView)
mainContainerView.addSubview(testContainerView.view)
// let dict = Dictionary(dictionaryLiteral: ("test_container", testContainerView))
// let constraint_h = NSLayoutConstraint.constraintsWithVisualFormat("H:|[test_container]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: dict)
// let constraint_v = NSLayoutConstraint.constraintsWithVisualFormat("V:|[test_container]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: dict)
mainContainerView.frame = testContainerView.view.frame
// mainContainerView.addConstraints(constraint_h)
// mainContainerView.addConstraints(constraint_v)
testContainerView.didMoveToParentViewController(self)
}
}
The view controller looks like this... and the code for it is pasted below. As can be seen, the story board has the image and the viewDidLoad sets it to yellow. However, if I turn off background color, no image is seen.
import UIKit
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.backgroundColor = UIColor.yellowColor()
}
}
How do I get the TestControllerView to display the views inside it?
You are instantiating the TestViewController like so:
let testContainerView = TestViewController()
That will not use the storyboard. If you want to use the storyboard, you have give that scene a "storyboard id" in Interface Builder, and then you can programmatically to do something like:
let testContainerView = storyboard?.instantiateViewControllerWithIdentifier("TestViewController storyboard id here")
By the way, when you have these sorts of problems, it's useful to run the app from Xcode and then use the view debugger:
That lets you dynamically inspect the view hierarchy (so you can confirm whether something's really missing or just possibly off screen or otherwise not visible).