I have a cell with subviews.
I can't figure out why the UIView boom isn't visible. Here is my code:
let separator: UIView = {
let view = UIView()
view.backgroundColor = .yellow
return view
}()
let boom: UIView = {
let b = UIView()
b.backgroundColor = .red
return b
}()
override func setupViews() {
super.setupViews()
addSubview(separator)
addSubview(setNumberView)
addSubview(boom)
backgroundColor = .orange
addConstraintsWithFormat("H:|-20-[v0]", views: boom)
addConstraintsWithFormat("V:|-20-[v0]", views: boom)
addConstraintsWithFormat("H:|[v0]|", views: separator)
addConstraintsWithFormat("V:[v0(10)]|", views: separator)
separator shows up as it is supposed to. Is there a bug in my xcode or something? I have tried restarting xcode, putting the view into a frame, and changing the cell size.
You are not setting any width or height to your view.
To properly setup the position of a view, you have to specify the horizontal position, the vertical position, the width and height.
The separator correctly specifies all of them, the view is missing constraints for width and height.
A way to fix that could be for example:
addConstraintsWithFormat("H:|-20-[v0]-20-|", views: boom)
addConstraintsWithFormat("V:|-20-[v0(100)]", views: boom)
Related
I'd like to use a scrollView to move the nested view content up when the keyboard appears. (Maybe you know a better solution ?)
So, I put a UIScrollView into my UIViewController and a UIImageView into my UIScrollView. The problem is my UIScrollView is as large as my image size despite constraints.
I put the following constraints :
scrollView.addConstraintsWithFormat(format: "H:|[v0]|", views: backgroundImage)
scrollView.addConstraintsWithFormat(format: "V:|[v0]|", views: backgroundImage)
self.view.addConstraintsWithFormat(format: "H:|[v0]|", views: scrollView)
self.view.addConstraintsWithFormat(format: "V:|[v0]|", views: scrollView)
Someone have a solution ?
This is my full UIViewController code :
import UIKit
class HomeViewController: UIViewController {
let scrollView: UIScrollView = {
let screenSize = UIScreen.main.bounds
let scrollView = UIScrollView()
scrollView.backgroundColor = .red
scrollView.contentSize = CGSize(width: screenSize.width, height: screenSize.height)
return scrollView
}()
let backgroundImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "BACKGROUND_ASIA")
imageView.alpha = 0.5
return imageView
}()
override func viewDidLoad() {
setupHomeView()
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func setupHomeView() {
self.view.backgroundColor = UIColor.black
self.view.addSubview(scrollView)
self.view.addConstraintsWithFormat(format: "H:|[v0]|", views: scrollView)
self.view.addConstraintsWithFormat(format: "V:|[v0]|", views: scrollView)
scrollView.addSubview(backgroundImage)
scrollView.addConstraintsWithFormat(format: "H:|[v0]|", views: backgroundImage)
scrollView.addConstraintsWithFormat(format: "V:|[v0]|", views: backgroundImage)
}
}
extension UIView {
func addConstraintsWithFormat(format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
You should call super first in viewDidLoad.
You should read up on how scrollViews work.
Here's what you need:
The ScrollView needs constraints for left/right/top/bottom.
This will determine the size of the presentable portion of the scrollview. This is the part that you would resize when the keyboard shows.
Then, you need to set the size of the ScrollView's content. This is the content that can be scrolled. You will need to manually set the size of your imageView, or setup equality between your imageView and views that exist outside of your scrollview. (eg imageView.width == view.width).
Hope this points in the right direction. You might want to consider using Interface Builder to set this up so you can see all the constraints and get warning when things aren't set up properly.
Thanks for your answer PEEJWEEJ, but I found another alternative to my problem. I used the NotificationCenter to notify keyboard opening and I made a view.animate() to scroll my view. By this way I avoid to use a scrollView or a tableView.
So i am using a custom function to format an subview that I am adding to a UICollectionViewCell. It is from Brian Voong's public project here: https://github.com/purelyswift/facebook_feed_dynamic_cell_content/blob/master/facebookfeed2/ViewController.swift.
func addConstraintsWithFormat(format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerate() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
What is interesting, is that in my UICollectionView I add a SubView to a single cell, and set the background color to white. The background is white when I comment out the line which sets the background for the subview, and no background color is set when I uncomment out the line setting the visually formatted constraints for the subview.
Here are the two lines which clobber each other:
func chronicleOneClicked(sender: UIButton) {
point1view.backgroundColor = UIColor.whiteColor()
addSubview(point1view)
//When the below is commented the background of point1view disappears
//addConstraintsWithFormat("|-50-[v0]-50-|", views: point1view)
}
when I do print(subviews) i see that the UIView with the white background color is the highest in the view stack (top of the stack). When i print out subviews[subviews.count-1].backgroundColor I get the Optional(UIDeviceWhiteColorSpace 1 1) which is what I expect. it is strange because the color is not displayed.
I am not sure how to go about seeing what is happening behind the scenes to confirm that the background is being set at all in the latter case.
This all happens in a class for the UiCollectionViewCell which I am using as the class of one of my UICollectionView Cells which can be viewed in its entirety here:
https://gist.github.com/ebbnormal/edb79a15dab4797946e0d1f6905c2dd0
Here is a screen shot from both cases, the first case is where the line addConstraintsWithFormat is commented out, and the second case is where it is uncommented: The subview of point1subview is highlighted with a white background in the first case.
This is how I setup the views. It all happens in a class that overrides UICollectionViewCell
class myClass : UICollectionViewCell {
var chronicle: BrowsableChronicle? {
didSet{
//etc.
point1.addTarget(self, action: #selector(chronicleOneClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
let point1 : PointButtonView = {
let pointView = PointButtonView(frame: CGRectMake(0, 0, 25, 25 ))
return pointView
}()
//NOTE here is where I create the view, whose background doesn't display
let point1view : UIView = {
let pointView = UIView(frame: CGRectMake( 0, 0, 200, 270))
pointView.backgroundColor = UIColor.whiteColor()
let title = UILabel(frame: CGRectMake(0, 0, 200, 21))
title.font = UIFont(name:"HelveticaNeue-Bold", size: 16.0)
pointView.addSubview(title)
let summary = UILabel(frame: CGRectMake(0, 0, 190, 260))
summary.lineBreakMode = NSLineBreakMode.ByWordWrapping
summary.numberOfLines = 4
summary.font = UIFont(name:"HelveticaNeue", size: 12.5)
pointView.addSubview(summary)
let button = UIButton(frame: CGRectMake(0, 200, 190, 30))
button.backgroundColor = UIColor(red:0.00, green:0.90, blue:0.93, alpha:1.0)
pointView.addSubview(button)
pointView.tag = 100
return pointView
}()
//NOTE: here is where I add the subview to the UICollectionViewCell view
func chronicleOneClicked(sender: UIButton){
addSubview(point1view)
addConstraintsWithFormat("H:|-20-[v0]-20-|", views: point1view)
//TODO anytime i add a constraint here the background color leaves!
print(subviews[subviews.count-1].backgroundColor) //Prints white
}
}
UPDATE: I thought maybe it was related to this issue :
UITableViewCell subview disappears when cell is selected
Where the UICollectionViewCell is selected, and therefore iOS automatically sets the backgroundColor to clear. The problem is, that I implemented this class extension of UIView to see when didSet is called on the backgroundColor and when it is set to clear, i set it to white. However, it only calls didSet on the backgroundColor once, when i first set the color of the view. Here is the code I used to override the UIView class:
class NeverClearView: UIView {
override var backgroundColor: UIColor? {
didSet {
print("background color is being set")
if backgroundColor == UIColor.clearColor() {
print("set to a clear color")
backgroundColor = UIColor.whiteColor()
}
}
}
}
The difference you are seeing is obviously caused by a view frame resulting in zero width or zero height.
Let's explain how the drawing system works.
Every view has a layer that draws its background color in its bounds, which are specified by the view frame. Then every subview is drawn. However, the subviews are not limited by the frame unless you set UIView.clipsToBounds to true.
What you are seeing means the a container view has a zero frame (either width or height) but its subviews have correct frame, therefore they are displayed correctly.
There are multiple reasons why this could happen, for example:
You are setting translatesAutoresizingMaskIntoConstraints to false to some system view (e.g. the content view of the UICollectionView).
You have a constraint conflict, resulting in some important constraint to be removed (you should see a warning).
You are missing some constraints. Specifically, I don't see you setting vertical constraints.
You should be able to debug the problem using the view debugger in Xcode. Just open your app, click the view debugger button and print the recursive description of the cell. You should see a frame that is zero.
I'm trying to create custom reusable view, lets say QuestionView. Now I use this class to be extended by my QuestionView, so it loads view from my xib, then this view added as subview to self. It works ok in case if my view has constant height and width, but I need kind of this layout
This view's file's owner set to QuestionView.
I have label on top which connected with top, left and right via constraints but it's flexible in terms of height - label is multiline. Yes/No buttons view connected to bottom of label, left and right of superview and has constant height. Details view connected to bottom of buttons view, to left and to right, has constant height. So my QuestionView has flexible height. If I change text of label to 2 lines for example, my view should be stretched.
I have ViewController xib, where I put generic view and set its class to QuestionView.
I just add this view as subview of QuestionView so I think there is a problem with constraints between view and subview, I should add them? I tried to add left, right, top, bottom constraints between them with translatesAutoresizingMaskIntoConstraints set to false but anyway got strange (similar to height from xibs) superview(QuestionView) height, subview height is ok in runtime.
So what am I doing wrong here? Do I need to bind subview height to superview height somehow differently?
UPD. Here is screenshot in runtime, gray view is it's size in runtime, should be stretched to TextField bottom. Now it looks like it was false effect of ok subview height in runtime.
Here is my code now
import UIKit
protocol NibDefinable {
var nibName: String { get }
}
#IBDesignable
class NibLoadingView: UIView, NibDefinable {
var containerView: UIView!
var nibName: String {
return String(self.dynamicType)
}
override init(frame: CGRect) {
super.init(frame: frame)
nibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
nibSetup()
}
private func nibSetup() {
//clipsToBounds = true
containerView = loadViewFromNib()
containerView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(containerView)
addConstraint(.Top)
addConstraint(.Left)
addConstraint(.Bottom)
addConstraint(.Right)
}
private func addConstraint(attribute: NSLayoutAttribute) {
self.addConstraint(NSLayoutConstraint(item: self,
attribute: attribute,
relatedBy: .Equal,
toItem: containerView,
attribute: attribute,
multiplier: 1,
constant: 0.0
))
}
private func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
let nibView = nib.instantiateWithOwner(self, options: nil).first as! UIView
return nibView
}
}
First, you have to add a constraint between QuestionView and bottom of it's superview
Second, looks like problem might be with QuestionView's superview and its parent constraints.
UPD
the best tool to nail those kind of bugs - Reveal
I have a UIImageView, and a series of conflicting constraints set on it.
I set the active property of some of them to true or to false at different times, and it works—the image view is always where it's supposed to be.
However, in another method, I use it's frame to calculate the position of another view, and so I noticed that it's frame isn't where the image view appears. For example, the image view appears centered in the middle of the screen, but it's frame (I created another UIView and set it's frame to the image view's frame to test this), is in the bottom left—where the image view used to be before changing which constraints were active.
Is there a way I can update the frame so that it reflects where the image view actually is? Or, is there a way to get the true frame of the image view?
Here's the code (qrCode is the view I'm trying to arrange, myContainer is its superview):
self.qrCode.setTranslatesAutoresizingMaskIntoConstraints(false)
//this sets qrCode 0 pts from the bottom of its parent
self.bottom.active = false
//this centers qrCode vertically in its parent
self.center.active = true
//these set how far the parent is from the edges of its view
self.newLeft.active = true
self.newRight.active = true
let testView = UIView(frame: qrCode.frame)
testView.backgroundColor = UIColor.greenColor()
self.myContainer.addSubview(testView)
All this code positions the image view (qrCode) correctly, but it's frame (as shown by testView is in the wrong location—it's where qrCode was before the constraints were configured.
Here's the answer:
Do this in your viewdidload:
#IBOutlet weak var asdf = QQCustomUIViewForQRImageView()
override func viewDidLoad() {
super.viewDidLoad()
var floatiesW = 200 as CGFloat
var floatiesH = 200 as CGFloat
asdf!.frame = CGRectMake((self.view.bounds.width/2.0) - (floatiesW/2.0), (self.view.bounds.height/2.0) - (floatiesH/2.0), floatiesW, floatiesH)
asdf!.qrImageView.image = UIImage(named: "check")
}
Here's the UIView you'll need to use as a subclass, as per our conversation:
import UIKit
class QQCustomUIViewForQRImageView: UIView {
var qrImageView = UIImageView()
override init (frame : CGRect) {
super.init(frame : frame)
}
convenience init () {
self.init(frame:CGRect.zeroRect)
}
override func layoutSubviews() {
super.layoutSubviews()
self.backgroundColor = UIColor.redColor()
qrImageView.setTranslatesAutoresizingMaskIntoConstraints(false)
qrImageView.backgroundColor = UIColor.yellowColor()
self.addSubview(qrImageView)
let views: [NSObject : AnyObject] = [ "qrImageView" : qrImageView]
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-4-[qrImageView]-4-|", options: nil, metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-4-[qrImageView]-4-|", options: nil, metrics: nil, views: views))
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
that's 4 points on each side, this will resize for you, and the iamgeview is set with an image like I show in the view did load call above
I recently added a scrollview to my viewcontroller. However, this caused my layout to mess up completely.
Here's an image below.
(I gave the UIScrollView a temporary red background, to display, that it's clearly taking the full screen)
now. I have a bunch of things in this view. But to keep it simple I will focus on the top blue bar, which in my app is called "topBar"
First of, I define it in my class.
var topBar = UIView()
I remove the auto sizing, give it a color and add it to my scrollview.
//----------------- topBar ---------------//
topBar.setTranslatesAutoresizingMaskIntoConstraints(false)
topBar.backgroundColor = UIColor.formulaBlueColor()
self.scrollView.addSubview(topBar)
add it to my viewsDictionary:
var viewsDictionary = [ "topBar":topBar]
add the height to my metricsDictionary:
let metricsDictionary = ["topBarHeight":6]
set the height in a sizing constraint.
//sizing constraints
self.scrollView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"V:[topBar(topBarHeight)]", options: NSLayoutFormatOptions(0), metrics: metricsDictionary, views: viewsDictionary))
And finally the part that doesn't work. I /attempt/ to make it the full width of "scrollView"
// Horizontal Constraints
self.scrollView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[topBar]|", options: nil, metrics: nil, views: viewsDictionary))
and my vertical constraint to put it at the top.
// Vertical Constraints
self.scrollView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"V:|[topBar]", options: nil, metrics: nil, views: viewsDictionary))
Now as for my scrollview, (the one that's probably causing my layout headaches)
It's set up as follows:
as the very first thing in the class:
let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)
first thing in my viewDidLoad()
self.view.addSubview(scrollView)
scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
scrollView.scrollEnabled = true
and lastly my viewDidLayoutSubviews.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
scrollView.contentSize = CGSize(width:2000, height: 5678)
}
^ The width of the contentSize will be changed to the width of the screen (I only want vertical scrolling). But right now that's a minor issue compared to the layout problems I'm having
Any help as to why everything is squeezed together would be greatly appreciated!
I managed to fix it doing the following.
Defining my contentsize in viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.frame = view.bounds
scrollView.contentSize = CGSize(width:self.view.bounds.width, height: 5678)
}
and instead of making the view equal to a scrollview, I had to make it a subview of it.
I also had to make a subview of the scrollview, for all my content to work with constraints properly.
self.view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
contentView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentView)
and all my other objects was made subviews of the "contentView" and not the scrollview.