Get View height based on margin constraints after runtime Swift - ios

I have a circle in the centre of a screen with a margin constraint of 50 on either end. Hence, the width of the circle is dependent on the screen size.
So, what I tried was this:
Approach 1
I set up the margins in the storyboard to define the circle width (50 on left and right)
Then I used the following code:
#IBOutlet weak var helpButHeight: NSLayoutConstraint!
#IBOutlet weak var helpBut: UIButton!
ViewDidLoad:
helpButHeight.constant = helpBut.frame.size.width
This didn't work.
Since the screen width is 400, and the margin is 50 on either end, then helpBut.frame.size.width should have given me 300.
Instead it gave me 46.
Approach 2
This was my work-around:
ViewDidLoad:
let screenSize: CGRect = UIScreen.mainScreen().bounds
helpButHeight.constant = screenSize.width - 100
because 100 = 50 + 50, the two margins.
Works fine !
Question
Why did I have to do this? Why did the first approach not work? Why 46 and not 300?

The reason is that constraints haven't kicked in, in the viewDidLoad function. The lifecycle looks something like
viewDidLoad -- Constraints haven't set
viewWillAppear -- Constraints haven't set
viewWillLayoutSubviews -- Constraints are setting
viewDidLayoutSubviews -- Constraints are set
viewDidAppear -- Constraints are set

If you want any view to be in center just put the horizontal/vertical center constraint...No code required.. If you want to have padding just put the left and right constraints...Just to remind you don't use both of them together...It'll break...

Related

Swift 3: Get distance of button to bottom of screen

I want to get the distance of a button to the bottom of the screen in Swift 3.
I can see the correct value in the Storyboard when I look at the distance (alt key) but unfortunately I am not able to calculate it manually.
The value I am looking for is the same as the constant in the "Vertical Spacing to Bottom Layout" constraint for the button.
view.frame.maxY - randomButton.frame.maxY
gave me a value way too high.
view.frame.size.height - (button.frame.size.height + button.frame.origin.y)
I think its ok! Hope it helps
If your button is not a direct successor of the view controller's view (aka, the hierarchy is something like ViewController's View -> SomeOtherView->Button), you won't get the right math by simply using frames. You will have to translate the button's Y-position to the coordinate space of the window object or your view controller's main view.
Take a look at this question: Convert a UIView origin point to its Window coordinate system
let realOrigin = someView.convert(button.frame.origin, to: self.view)
Then apply the math suggested by Lucas Palaian.
let bottomSpace = view.frame.maxY - (button.frame.height + realOrigin.y)
Another work around, in case something really wild and wierd is going on there, is to drag and drop an outlet of the button's bottom constraint. (Select the constraint from the view hierarchy in Interface Builder, hold the control key and drag the constraint to your view controller.) Then in your code you can access the constant.
let bottomSpace = myButtonBottomConstraint.constant
Use this to have it from the bottom of the button to the bottom of the view (Tested):
view.frame.size.height - randomButton.frame.size.height/2 - randomButton.frame.origin.y
I needed to find the distance from the bottom edge of the UICollectionView
to the bottom edge of the screen.
This code works for me:
#IBOutlet weak var collectionView: UICollectionView!
private var bottomSpace = CGFloat()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
bottomSpace = UIScreen.main.bounds.height - collectionView.frame.maxY
}
This method is called several times it gives the correct result.

iOS-Autolayout design: Increase button height as device height changes

In my application I have added a UIButton with following constraints:
Leading space = 10
Trailing space = 10
Height = 50
Bottom space =10
By this I got a button placed at a bottom of device. It works good on single device(iPhone 4), but as device height increases button height remains same(iPhone 6).
I want to change button height also with Autolayout.
I tried some example which suggest to add multiplier value but no luck.
I am using Xcode-8 with Swift3
Please suggest me.
There is multiple way to handle this issue
First:-
Create 1,Give Fixed Height Constraint to button
2.Create #IBOutlet of Height Constraint
like this way
#IBOutlet weak var btnHeightConstraint: NSLayoutConstraint!
3. Set Constraint constant as per your requirement
like this way.
btnHeightConstraint.constant = 50 //your button height 50
btnHeightConstraint.constant = 60 //your button height 60
Set all the constarints which is needed and set 'Equal Height' constraint with your 'superview' and set the multiplier value i.e if your view size is same as iphone 5 then set multiplier value as 'yourHeight:568', you will get required output.

Giving Offset To Horizontally Centered Element Programmatically

I have set constraints on Storyboard, and tried to center CollectionView horizontally and give an extra offset.
I tried logging constraints on console by using this and consoled all the constraints.
print(collectionView.constraints)
How can I overwrite 30 offset to 50 offset (for example) programmatically?
Create an IBOutlet to the constraint you wish to overwrite.
#IBOutlet weak var collectionViewCenterXConstraint: NSLayoutConstraint!
then whenever you want to change it.
collectionViewCenterXConstraint.constant = 50 // the offset will be 50 now.

Dynamic height UITableViewCell

I have cells that consist of a title, date and a number of hashtags.
Here's the storyboard's screenshot:
Custom cell in storyboard
I've set the following in my ViewDidLoad:
tableView.rowHeight = UITableViewAutomaticDimension
But haven't given any estimatedHeight for the tableView which I'll explain why.
Here's my customCell:
#IBOutlet weak var title: UILabel!
#IBOutlet weak var date: UILabel!
#IBOutlet weak var hashtagsView: UIView!
var item: Item? {
didSet {
configureCell()
}
}
func configureCell() {
if item = item {
title.text = item!.title
date.text = item!.date
// The part where I calculate the sizes of hashtags to fit them in hashtagsView
let totalWidth = CGRectGetWidth(hashtagsView.frame)
print("TotalWidth: \(totalWidth)")
.
.
.
.
print("Content: \(self.contentView.frame)")
print("HashtagsView: \(self.hashtagsView.frame)")
}
}
Here's the results:
With tableView.estimatedHeight = 150
TotalWidth: 240.0
Content: (0.0, 0.0, 240.0, 119.666666666667)
HashtagsView: (0.0, 0.0, 240.0, 128.0)
Without estimatedHeight
TotalWidth: 240.0
Content: (0.0, 0.0, 414.0, 43.6666666666667)
HashtagsView: (0.0, 0.0, 240.0, 128.0)
Recap
When there is an estimatedHeight, cell's contentView doesn't print a correct width, but displays the cell's contents well like nothing's wrong (except for the hashtagsView).
When there's not an estimatedHeight, cell's contentView does actually print the correct width, which lets me calculate the hashtags frames, but the cells display with their default 44 height.
Detailed Info
Since I don't know how many hashtags there is, I'm trying to use the blank UIView to add the UIButtons programmatically. And for calculation purposes of the hashtags' buttons, I need to have the "width" of the cell's contentView, or the hashtagsView's, but when I set tableView.estimatedHeight, cell's width will be some arbitrary number (e.g. 240 in 6s Plus Simulator). And I just can't get the hashtagsView's width, even though I have no auto-layout issues.
And when I don't give tableView.estimatedHeight an estimation, I get the following:
Custom cells without estimatedHeight
Updated - An update asked by #EarlGray in the comments
The hashtags are actually UIButtons I add them to the hashtagsView dynamically. I need to stretch the hashtagsView's height so it'll fit more than one line of hashtags.
I think I'll either need to subclass UIView and override layoutSubviews() to achieve fit vertical layout or add constraints to each subview (UIButtons) programmatically.
Doing what #VinodVishwanath said, setting estimatedRowHeight combined with explicit heights and vertical space constraints gives me this:
Which unables me to get the width of the hashtagsView. During the calculations of the UIBUttons' frames, I need the cell.contentView's width, but somehow, setting the estimatedRowHeight gives me the following coordinates for the cell.contentView.frame
Content: (0.0, 0.0, 240.0, 119.666666666667)
Which is incorrect, because it has to give 414, and that's why my hashtags start from half of the screen.
Commenting out tableView.estimatedRowHeight gives me the correct coordinates:
Content: (0.0, 0.0, 414.0, 43.6666666666667)
But messes my tableView like so:
Update #2 - Here's my constraints for the cell.contentView
ContentView's constraints
Update #3 - A breakpoint on my configureCell method
HashtagsView's superview returns nil!!
I don't get it, my UIView IBOutlet is connected, I double checked.
All of the contentView.subviews have incorrect frames. So does the superview-less hashtagsView.
But when I remove estimatedRowHeight, it suddenly considers hashtagsView as a subview of cell's contentView. Except for contentView, it's subviews frames' still return incorrect and negative values.
When you don't provide the estimated row-height, the cell height defaults to 44, and that's why you're getting a content height of 43.667.
You need to provide the estimated row height, which will be the height when there are no tags in the field provided. Then all that's needed to set the correct height dynamically is the right set of autolayout constraints to provide the contentView's intrinsic size.
This will happen when you set a vertical space constraint between all the subviews of contentView, including a topSpaceToSuperview and bottomSpaceToSuperview to enable the AutoLayout engine to calculate the intrinsic content size.
Edit
I have analysed your constraints from the screenshot, and here are the relevant constraints you have added to calculate the cell height:
Title.top = topMargin + 8
bottomMargin = Share Button.bottom
Link.centerY = Share Button.centerY
Hashtags View.top = Title.bottom
Date.top = Hashtags View.bottom + 15
Now here's a visual representation of these constraints:
TopMargin
|
Title
|
Hashtags View
|
Date
!!! broken link !!!
Link — Share Button
|
BottomMargin
The broken link prevents the Layout Engine from calculating the cell height. You need to fix the broken link in the vertical layout, for instance, by setting Date.centerY = shareButton.centerY, or by setting Date.top = HashtagsView.bottom.

How to easily collapse vertical space around label when its text is nil?

Suppose I have three labels that are laid out below each other in a column. The uppermost label's top edge is pinned to the superview's top edge. All following labels' top edges are pinned to the preceding label's bottom edge. The leading and trailing edges of all labels are pinned to the leading and trailing edge of the superview. Here's what it looks like in Interface Builder (I added a blue background on every label to visualize its extent).
In the simulator the result looks like this.
All labels are connected to outlets in a view controller.
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var label2: UILabel!
#IBOutlet weak var label3: UILabel!
When I set the text of label2 to nil
label2.text = nil
the label itself collapses.
However, the top and bottom spaces around the label do not collapse. This is evident by the fact that there is no blue background on the middle label in the last screenshot. As a result, the space between label1 and label3 is double the space of the layout in the first screenshot.
My question is - on iOS8 - what is the easiest way to collapse either the middle label's top or bottom space so that the two remaining labels still use the vertical spacing defined in the original layout? To be clear, this is the result I want to achieve.
Options I've found so far:
Bottom/Top Spacing Constraint Outlet
Define an outlet for the middle label's top or bottom spacing constraint.
#IBOutlet weak var spacingConstraint: NSLayoutConstraint!
Store the constraint's initial constant into a variable (e.g. in awakeFromNib or viewDidLoad).
private var initialSpacing: CGFloat!
override func viewDidLoad() {
initialSpacing = spacingConstraint.constant
...
Set the constraint's constant to zero whenever the text is set to nil or back to its initial value when the text is not nil.
spacingConstraint.constant = label2.text == nil ? 0 : initialSpacing
This approach feels a bit clumsy since it requires two additional variables.
Height Constraint Outlet
Set the vertical spacing around the middle label to zero and increase its height by the same amount. Define an outlet for the height constraint and proceed as above, setting the height to zero when the text is nil and back to it's initial value when the height is not nil.
This is still as clumsy as the previous approach. In addition, you have to hardcode the spacing and cannot use the built-in default spacings (blank fields in Interface builder).
UIStackView
This is not an option since UIStackView is only available on iOS 9 and above.
I'm using this UIView category for this purpose.
It extends UIView by adding two more property named fd_collapsed and fd_collapsibleConstraints using objective-c runtime framework. You simply drag constraints that you want to be disabled when fd_collapsed property set to YES. Behind the scene, it captures the initial value of these constraints, then set to zero whenever fd_collapsed is YES. Reset to initial values when fd_collapsed is NO.
There is also another property called fd_autocollapsed
Not every view needs to add a width or height constraint, views like UILabel, UIImageView have their Intrinsic content size when they have content in it. For these views, we provide a Auto collapse property, when its content is gone, selected constraints will collapse automatically.
This property automatically sets fd_collapsed property to YES whenever specified view has content to display.
It's really simple to use. It's kinda shame that there is no builtin solution like that.
Your solutions are good enough for me and I'd do Bottom/Top Spacing Constraint Outlet solution but since you want something different. You can use this third party: https://github.com/orta/ORStackView It has iOS7+ support and do exactly what you need.
This is low-key a pain all perfectionist devs learn about when trying to stack a bunch of labels. Solutions can get too verbose, annoying to folow, and really annoying to implement (ie. keeping a reference to the top constraint... gets annoying once you do it multiple times, or just change the order of the labels)
Hopefully my code below puts an end to this:
class MyLabel: UILabel {
var topPadding: CGFloat = 0
override func drawText(in rect: CGRect) {
var newRect = rect
newRect.origin.y += topPadding/2
super.drawText(in: newRect)
}
override var intrinsicContentSize: CGSize {
var newIntrisicSize = super.intrinsicContentSize
guard newIntrisicSize != .zero else {
return .zero
}
newIntrisicSize.height += topPadding
return newIntrisicSize
}
}
Usage:
let label = MyLabel()
label.topPadding = 10
// then use autolayout to stack your labels with 0 offset
Granted, its only for top padding, but that should be the only thing you need to layout your labels properly. It works great with or without autolayout. Also its a big plus not needing to do any extra mental gymnastics just to do something so simple. Enjoy!

Resources