I have a UIView on a XIB, containing an UIImageView and a UILabel with a small space in between. Horizontal layout is a simple chained |--image--label--| in which -- is some fixed space. The height is fixed to 40, this view is horizontally centred in its view controller, and it has an >= 100 width constraint.
If I change the label text, the width of my composed view updates as expected width the changed width of its label, and it stays nicely centred on the view controller.
Because I need this UIView, containing an image and label, in other places, I've created a custom class consisting of a XIB and Swift file. Let's call it ItemView.
Issue I have is that the empty UIView on my view controller XIB, which I've changed class to ItemView, no longer accepts the >= 40 width constraint. This is of course because the view controller XIB no longer sees the variable width UILabel, but instead just a plain UIView of class ItemView. I get an 'Inequality Constraint Ambiguity' IB error.
The result is that the width of my custom view remains 40. It works a little bit if I specify a larger >= label width; the text is then only cut off when this width is reached. But in that second case my custom view is no longer horizontally centred, but shifted a bit to the left instead.
How do I resolve this? Or, how can I tell IB to treat my custom ItemView in a similar way as a UILabel?
In my ItemView I've done all I could find:
override class var requiresConstraintBasedLayout: Bool
{
return true
}
Call setNeedsLayout() after setting the label text.
Call myLabel.translatesAutoresizingMaskIntoConstraints = false.
Call self.translatesAutoresizingMaskIntoConstraints = false.
And call self.setNeedsUpdateConstraints() in both init()s.
Configure this pop-up in the view's Size inspector:
Now IB won't worry about your size specifications. You know better than IB does, and this is how to tell IB that fact.
Another way: configure this pop-up in the view's Size inspector:
This tells IB that the view will have an intrinsic content size that it doesn't know about.
Either of those will work. As you can see in this screenshot, I've given my custom view a width constraint of greater-than-or-equal-to-40, but IB is not complaining of any error:
How I structured my custom UIView: My XIB's Files Owner is ItemView. ItemView has an #IBOutlet var: UIView! that's connected to the view in the XIB. Then I have:
init()
{
super.init(frame: .zero)
self.initView()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
self.initView()
}
private func initView()
{
Bundle.main.loadNibNamed("ItemView", owner: self, options: nil)
self.view.frame = CGRect(x: 0.0, y: 0.0, width: self.width, height: self.height)
self.addSubview(self.view!)
}
(I seems this extra UIView is needed when creating a custom UIView with a XIB. Would love to hear if it's not needed after all.)
To make it work I needed to call self.invalidateIntrinsicContentSize() after updating the label. And override intrinsicContentSize:
override open var intrinsicContentSize: CGSize
{
return self.view.systemLayoutSizeFitting(self.bounds.size)
}
Note that I'm calling this on self.view, not on self.
Thanks #matt for pointing me in the right direction. Was the first time I've encountered something like this.
The following things I tried we all not necessary:
override class var requiresConstraintBasedLayout: Bool
{
return true
}
Call setNeedsLayout() after setting the label text.
Call myLabel.translatesAutoresizingMaskIntoConstraints = false.
Call self.translatesAutoresizingMaskIntoConstraints = false.
And call self.setNeedsUpdateConstraints() in both init()s.
Related
Currently, I am implementing custom view from a xib with Content View size set to Freeform. Here is my Content View hierarchy
Although the Content View is Freeform, I set the width to 375, which is the same width with iPhone 8. Then I create a custom UIView file
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("DetailsWeather", owner: self, options: nil)
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
}
After that, I set the xib File’s Owner custom class name to the same custom UIView file above. Then I implement it into Main.storyboard by adding a UIView, set constrains properly and give it the custom class name the same with File’s Owner
When running iPhone 8, everything is perfect but switching to smaller device like iPhone 5s, I notice my scroll view now have horizontal scroll. Contrast with iPhone 5s, the bigger screen device like iPhone 8+, my scroll view now lost a bit of to the right side.
Notice the labels does not align with the clock which is center on iP8+ anymore
So I tried to remove the scroll view and everything work normal across devices. So from these, I was thinking the scroll view must messed up my custom view. Then I do some self research and found out these topics.
How to update constraints after adding UIView from xib on UIScrollView in swift?
ScrollView Add SubView in swift 3
I tried their solution and modified to fit my situation but none of them seem to work with me. So my question is, is there a way to make Content View of a xib file to fit every width?
Just solved today, feels great tbh
Remove all above step, just keep the IBOutlet in your custom UIView file
Remove File’s Owner custom class name and replace Content View custom class name to the same name with your custom UIView
Add UIView into your Main.storyboard, my case is ScrollView. Add all required constrains (duh)
Add IBOutlet for the ScrollView like this #IBOutlet weak var scrollView: UIScrollView!
Navigate to your View Controller file
let alohaView = Bundle.main.loadNibNamed("Feature", owner: self, options: nil)?.first as? FeatureView with Feature is your xib file and FeatureView is your custom UIView file control that xib file
Add alohaView?.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 500) notice I hardcode the 500 height, will work more on this
scrollView.contentSize = CGSize(width: self.view.frame.size.width, height: (alohaView?.frame.size.height)!)
scrollView.addSubview(alohaView!) add the custom view back to scroll view
I have the following scenario:
I have a xib file of name View1.xib which contain some stack views containing certain set of buttons.
View1.xib UIView
Im having a coco touch class of type UIView for loading the Nib named View1. Also I have made it as owner of the previous xib. Below is the code I'm using to load the view:
override init(frame: CGRect) {
super.init(frame: frame)
commitInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commitInit()
}
private func commitInit() {
Bundle.main.loadNibNamed("View1", owner: self, options: nil)
containerView.frame = self.bounds
containerView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(containerView)
}
Now in the storyboard I included the scrollview and a sub UIView under it. I have defined the Subview type as View1.
ScrollView in main storyboard
Also I have defined proper constraints for the Views making it equal height and width as the Parent ScrollView.
Scrollview constraints
Also find the scrollview properties defined as below:
ScrollView Properties
This is how the screen looks like on emulator :
Emulator
Now when I'm running the project I'm getting the screen but I'm unable to scroll through the view included. Due to this some elements on top and bottom becomes hidden (Out of bounds) and I'm unable to scroll as well. Am I doing anything wrong? Please help me out.
Note - I'm very new to swift, any help is greatly appreciated. Thanks in advance.
Your newView can't flow out of scrollView's bound, Because it has same height with your scrollView. But your newView's content flows out of newView's bounds.(You can see if you put your newView out of scrollView with same height and set it a backgroundColor or set clipToBounds property true) That's why you can't scroll.
To Sove This, you should find another source for your newView's height(Maybe a constant value or its subview's heihgt or safeArea.heihgt . This is your choice)
For subview add
yourView.translatesAutoresizingMaskIntoConstraints = false, then left, right, top, bottom constraint programatically then add subview.
For scroll view set the height constraint for inner view with the scroll view. Make the outlet of that constraint and then set dynamic height of the scrollview outlet.
I have three child VCs that are added to a parent VC. In one of my child VC, I have a view being loaded from a nib. In that nib, I set up a UITextView with no constraints and disabled scrolling so that it dynamically sizes depending on the text. This all works fine so far, I can enter or remove text from the UITextView and it resizes accordingly.
However, if I switch tabs (child VCs), and return back to the one with my UITextView, my UITextView is now of height 0 (cannot be seen). I'm not sure what is causing this to happen, everything works fine until I switch views and return.
My first thought would be to reconfigure my UITextView on viewDidAppear() when returning to my child VC, except my UITextView outlet and setup method is in a separate UIView subclass so I cannot call viewDidAppear(). I'm not even sure if that would be a fix, just what I would guess.
MyParent.swift: UIViewController
MyChild1.swift: UIViewController
MyChild2.swift: UIViewController
MyChild3.swift: UIViewController
MyView.swift: UIView - Custom View loaded from Nib
(this is where my UITextView outlet is and setup for it)
In MyChild3 for example I create the custom view with:
let view = Bundle.main.loadNibNamed("MyView", owner: self, options: nil)?.first! as! MyView
view.myModel = model
view.configure()
Then, in MyView.swift which is the custom class that the nib uses, I have my UITextView outlet and setup method:
#IBOutlet weak var textView: UITextView!
public func configure() {
configureTextView()
}
private func configureTextView() {
textView.delegate = self
textView.text = myModel.text
textView.isScrollEnabled = false
}
EDIT:
I should mention that my UITextView is a part of a horizontal StackView along with a UIImageView, as follows:
UIImageView
UITextView
When I return to my VC, the UIImageView takes up 100% of the height in the StackView, although before leaving my VC, the UITextView height was sized correctly to its text. After doing Show View Hierarchy, I cannot even find my UITextView anymore, only the UIImageView seems to be in the StackView. I essentially want my UITextView to take up as much height as it needs in the StackView, and the UIImageView take up the rest, but the UIImageView is taking it all up.
I did manage to find this error?
I would try setting content compression resistance of the textView to a higher value, maybe that's what causing ambiguity:
textView.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 999), for: .vertical)
Adding proportional fill setting in a UIStackview won't let the view stretch properly. Is this the expected behaviour?
Fill proportionally' distribution type works with intrinsic content size.
So if our vertical stack(height say 600) view has 2 views, ViewA (intrinsic content height 200) and ViewB(intrinsic content height 100), the stack view will size them to ViewA(height 400) and ViewB(height 200).
Here in IB what you see is not what you get.
Dragging to make frames change is useless. Just run the app.
You will see the expected behaviour only when the child views somehow get the intrinsic/constrained height.
How it looks in IB Here the top stack view has views constrained to be of height minimum 10 and 30, i.e. ration 1:4.
What we really get
Top stack view is what we had expected to look like. View with height in ratio 1:4. And bottom one with ratio 1:1, not expected.
You can fix this by creating a custom view
class CustomHeightView: UIView {
var height = 1.0
override public var intrinsicContentSize: CGSize {
return CGSize(width: 0.0, height: height)
}
}
Change the class of your UIView to CustomHeightView
So in the Controller create an outlet for the UIViews
#IBOutlet weak var header_one: CustomHeightView!
#IBOutlet weak var header_two: CustomHeightView!
Then in your viewDidLoad set the proportion the way you want it
override func viewDidLoad() {
super.viewDidLoad()
header_one.height = 1.8
header_two.height = 1.2
}
So I have a custom UIView class
class MessageBox: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
createSubViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
createSubViews()
}
func createSubViews() {
let testView = UIView(frame: self.frame)
testView.backgroundColor = UIColor.brown
self.addSubview(testView)
}
}
I added a UIView inside the storyboard and gave it some constraints:
100 from the top (superview), 0 from the left and right, height is 180
But when I run the app the brown subview I created in the code is way to big. I printed self.frame in my custom view and it turns out that the frame is (0,0,1000,1000). But why? I set constraints, it should be something like (0,0,deviceWith, 180).
What did I do wrong?
EDIT: That's my Storyboard setup:
Short and simple answer:
You're doing it too early.
Detailed answer:
When a view is initialized from an Interface Builder file (a xib or a storyboard) its frame is initially set to the frame it has in Interface Builder. You can look at it as a temporary placeholder.
When using Auto Layout the constraints are resolved (= the view's actual frame is computed) inside the view's layoutSubviews() method.
Thus, there are two possible solutions for your problem:
(preferrable) If you use Auto Layout, use it throughout your view.
Either add your testView in Interface Builder as well and create an outlet for it
or create your testView in code as you do, then set its translatesAutoResizingMaskIntoConstraints property to false (to sort of "activate Auto Layout") and add the required constraints for it in code.
Set your testView's frame after the MessageBox view's frame itself has been set by the layout engine. The only place where you can be sure that the system has resolved the view's frame from the constraints is when layoutSubviews() is called.
override func layoutSubviews() {
super.layoutSubviews()
testView.frame = self.frame
}
(You need to declare your testView as a property / global variable, of course.)
Try to use the anchors for your view:
MessageBox.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor).active
= true
MessageBox.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor).active
= true
MessageBox.widthAnchor.constraintEqualToConstant(150).active = true
MessageBox.heightAnchor.constraintEqualToConstant(100).active = true
This method have to be used inside your class
override func layoutSubviews() {
super.layoutSubviews()
testView.frame = self.frame
}
this also works when you add a custom class to a UIView in the storyboard and that uses autolayout.
thanks Mischa !
try to add a height and width constraint relative to the superview height, with some multiplier.