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
Related
I have a UICollectionViewController embedded inside a UINavigationController which in turn embedded inside a UITabBarController.
I want to add a UIView to the UICollectionViewController just above the tab bar (shown by red rectangle).
I have the UIView created separately as a nib file.
import UIKit
class BottomView: UIView {
#IBOutlet var view: UIView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
fileprivate func commonInit() {
Bundle.main.loadNibNamed("BottomView", owner: self, options: nil)
view.frame = self.frame
view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(view)
}
}
And I initialize and add it in the UICollectionViewController like so.
class CollectionViewController: UICollectionViewController {
fileprivate var bottomView: BottomView!
override func viewDidLoad() {
super.viewDidLoad()
let yPos = view.bounds.height - (tabBarController!.tabBar.frame.size.height + 44)
bottomView = BottomView(frame: CGRect(x: 0, y: yPos, width: view.bounds.width, height: 44))
collectionView?.addSubview(bottomView)
}
// ...
}
I figured if I substract the combined height of the bottom view plus the tab bar from the entire view's height, I should be able to get the correct y position value. But it's not happening. The view is getting added but way off screen.
How do I calculate the correct y position without hardcoding it?
Example demo project
I would suggest adding the BottomView to the UICollectionViewController's view rather than to the collection view itself. This is part of the problem you're having.
You're also trying to set the frame of the BottomView in the viewDidLoad() method using values from view.bounds. The CGRect will return (0.0, 0.0, 0.0, 0.0) at this point because the layout has yet to take place, which is most likely why your positioning is off. Try moving your layout logic to the viewWillLayoutSubviews() method and see if that helps.
A better approach would be by setting auto layout constrains rather than defining a frame manually, this will take a lot of the leg work out for you.
Here's a quick example:
self.bottomView.translatesAutoresizingMaskIntoConstraints = false
self.view.insertSubview(self.bottomView, at: 0)
self.bottomView.bottomAnchor.constraint(equalTo: self.view.layoutMarginsGuide.bottomAnchor).isActive = true
self.bottomView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
self.bottomView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
self.bottomView.heightAnchor.constraint(equalToConstant: 100.0).isActive = true
You can apply autolayout logic in your viewDidLoad() and it should work correctly.
You can find some more information on setting autolayout constraints programatically here:
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html
Sounds what you want to achieve is exactly the footer view for the UICollectionView.
A footerView is like a view that will stick to the bottom of the collectionView and wont move with the cells.
This will help you add a footer View: https://stackoverflow.com/a/26893334/3165112
Hope that helps!
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)
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.
So I wrote my own custom view with its own initializer. However, when my main view loads my custom view gets depicted in a wrong way. It takes bounds as 600x600 rectangle, while the superview is 375x607. I did try to put auto constraint, seems not to work. I tried to do it programmatically in the subview initialization, but whenever I try to initialize it's bounds property to its superview bounds I get nil in superview.
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
func setup() {
initPathLayer()
initHandleView()
initHandlePanGestureRecognizer()
layoutPathLayer()
layoutHandleViews()
}
I tried everything there is on the internet to make subview fill its superview, but I think that subview gets initialized before superview? Is that possible ? In the ViewController a have my custom view declared as an Outlet connection. I'm sure that the problem should be super easy and it's me who doesn't know the way Swift initializes the view.
Any ideas ?
Thank You.
Firstly, a Views init method is not the best place to perform a layout. The view could be resized at a later point which is typically always the case if the view is loaded from a Xib or you are creating it in a View Controllers viewDidLoad function.
That being said you have several approaches:
1. Use Auto Layout
Interface Builder
This can be done either in Interface Builder or programmatically. In Interface Builder you simply use the 'Pin' option and select all the sides of the view
When this is done you should be able to see your constraints in the Size inspector looking as follows:
Programmatically
Alternatively you can always add your constraints programmatically in your initializer:
override init(frame: CGRect) {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = 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)
}
convenience required init(coder aDecoder: NSCoder) {
self.init(frame: .zero)
}
2. Manual Layout
Resizing Masks
For this you can either use resizing masks or you can control the frame in layoutSubviews. Resizing masks tell the system how the view should be sized relative to the superview:
override init(frame: CGRect) {
let view = UIView(frame: CGRectZero)
view.translatesAutoresizingMaskIntoConstraints = false
super.init(frame: frame)
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
}
Layout Subviews
Lastly, you can override layoutSubviews and go from there:
override func layoutSubviews() {
super.layoutSubviews()
view.frame = bounds
}