storyboard dynamic auto layout issue - ios

I have this situation :
When I tap on "add" button I reduce the pink view's(first view) height and I execute this code:
#IBOutlet weak var viewPink: UIView!
#IBAction func add(_ sender: AnyObject) {
viewPink.frame = CGRect(x: viewPink.frame.origin.x, y: viewPink.frame.origin.y, width: viewPink.frame.size.width, height: viewPink.frame.size.height - 50)
}
but I want that the last view remains to the same distance from the pink view , essentially you have to climb on why the pink view reduces its height , instead the second view remains where it was before.
Can you help me about it?
P.S I set the vertical spacing constraint between the two views but It doesn't work

You should add an Height constraint on your pink view, create an IBOutlet to this constraint in your ViewController, and set the "constant" property to change the height.
Example:
heightConstraint.constant = 150
This will change the height with Autolayout, you shouldn't change the height by setting a new frame because it doesn't use Autolayout.

Related

Shrink Navigation Bar on scroll based on a Scroll View

I have a Scroll View set to a fixed height inside my View Controller. I want to use a navigation bar on top with large titles, so when i scroll the Scroll View, it should collapse like in a Navigation Controller. Is it possible to do this? My scene look like this:
The navigation bar has top/left/right 0 constraints agains the View. Currently it stays on top correctly, however it won't collapse on scroll as expected.
Do not use a "loose" navigation bar like this. Use a navigation controller, even if you do not intend to do any navigation. It gives you the desired behavior, for free.
In the end i created a custom view to replicate the Navigation Bar. Here you can see how it looks and read the steps below to replicate:
To setup your View Controller to be used with a custom Scroll View, first make sure you are using Freeform size for your controller. To do this, select Freeform in the size inspector and set the height to your new Scroll View's height:
Insert your Scroll View and setup 0 top/left/right/bottom constraints, so it will be the same size as your View Controller:
Add your content to your scroll view as usual
Now to create your custom Navigation Bar, add a View outside of your Scroll View and setup constraints like this:
Notice a few things:
the top constraint is aligned to the Superview instead of the Safe Area, so the view goes behind the status bar
The height is set to >= 44, so its a minimum height and can expand if the content is larger
On the Attribute Inspector, select clip to bounds, so your content inside the view won't overflow(like in CSS, overflow:hidden)
At this point you might see some errors in your Storyboard, but don't worry about it: its because you don't have any content in your View and it doesn't know how tall it should be
Set the View background to transparent and add a "Visual Effect View with Blur" inside, with 0 top/left/right/bottom constraints. This will blur the content behind the custom navigation bar
Now make sure that you check the Safe Area Layout Guide checkbox in your navigation bar view(its above the constraints setup):
This way you can add content inside the view that won't be behind the status bar, because its outside of the safe area. And it works with the notch too.
Add a label inside your view, set top and bottom constraints to Safe Area and make sure you have a fixed height constraint defined too:
Now you can also see that the errors in your Storyboard are gone :) At this point this is how everything should look like:
Now the coding part. In your ViewController, make outlets for both the ScrollView and the custom navigation bar. To do this, switch to the assistant editor(the venn-diagram symbol top right), select the element in your storyboard, hold down CTRL and drag inside your ViewController class:
Do the same for your View that is your navigation bar:
#IBOutlet weak var mainScrollView: UIScrollView!
#IBOutlet weak var customNavigationBar: UIView!
Next, you need to add the UIScrollViewDelegate to your class, so you can listen to the scroll event and get the actual Y position of the current scroll offset using the scrollViewDidScroll function:
class ViewController: UIViewController, UIScrollViewDelegate {
You also need to setup the delegate in your viewDidLoad hook:
mainScrollView.delegate = self
Create a new function called scrollViewDidScroll to get the scroll position and you can use this to do various animations with other elements. In this case, if the scroll position reaches 44(this is the height i set for my custom navigation bar), it will animate to full opacity:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let y = self.mainScrollView.contentOffset.y
let barHeight = 44
if(y < barHeight) {
customNavigationBar.alpha = y/CGFloat(barHeight)
} else {
customNavigationBar.alpha = 1
}
}
You can use the same logic to animate the label inside the navigation bar, change its size etc...
The full ViewController:
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var mainScrollView: UIScrollView!
#IBOutlet weak var customNavigationBar: UIView!
override func viewDidLoad() {
super.viewDidLoad()
mainScrollView.delegate = self
customNavigationBar.alpha = 0
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let y = self.mainScrollView.contentOffset.y
let barHeight = 44
if(y < 44) {
customNavigationBar.alpha = y/CGFloat(barHeight)
} else {
customNavigationBar.alpha = 1
}
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var height = CGFloat()
if(scrollView.panGestureRecognizer.translation(in: scrollView.superview).y > 0) {
height = 130
}
else {
height = 44
}
UIView.animate(withDuration: 0.5) {
self.navBarHeightConstraint?.constant = height
self.view.layoutIfNeeded()
}
}

Autoresize subviews when the frame of superview is modified

I have a green rectangle (viewA) with a inner red rectangle (viewB). If I try to modify the dimension of the viewA the viewB is not automatically changed.
I try to:
check the autoresizesSubviews value (is true)
set an autoresizingMask
change the bounds instead the frame of the superview
All these solution don't work.
This is my code:
#IBOutlet weak var viewA: UIView!
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonAction(_ sender: Any) {
viewA.autoresizesSubviews = true
self.viewA.autoresizingMask = [.flexibleWidth, .flexibleHeight,.flexibleTopMargin, .flexibleLeftMargin, .flexibleRightMargin, .flexibleBottomMargin]
viewA.bounds = CGRect(x: 0, y: 0, width: 100, height: 100) // or frame instead of bounds
}
Thanks to everyone.
EDIT:
I add the two views in the storyboard. Here you can see also the constraint of the viewB.
Don't mix up frame and auto layout of a view, both are behaving differently.
You need to do something like as follow.
Constrains for viewA
CenterX and CenterY
(Height or Width) and Aspect Ratio 1:1 (means equal width and equal height)
Constraints for viewB
Leading, Trailing, Top and Bottom to its superview (i.e. viewA)
Now you need to Create an #IBOutlet to the constraint you need to modify(of viewA).
#IBOutlet weak var const_height_viewA: NSLayoutConstraints!
So on button click you need to change only constraint's constant.
#IBAction func buttonAction(_ sender: Any) {
const_height_viewA.constant = 250
//changing height constraint of viewA will automatically updates viewB frame.
}
Note: No need to modify constraints of viewB as it will be automatically adjusted by given constraints.
For pictorial reference
You should not change the frame if you are using AutoLayout. If you are using AutoLayout, it is best to update the constraints to the values you want instead of changing the frame.
Notice the first point in the Rule of Thumb section from the docs.

UIStackview fill proportionally in a UITableviewCell takes incorrect frame

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
}

How do I display a UIImageView via a Switch on state? Then...how do I add constraints to the UIImageView to either center it or such?

I am trying to have a UIImageView (centered horizontally in the viewController) with a UISwitch inside of it.
When the UIswitch is turned to the ON position - a second UIImageView should appear to the left of the original UIImageView, while the constraints should make it so that both UIImageViews are centered horizontally.
thanks in advance
There are many ways to do this, but the easiest is to simply use a centered horizontal UIStackView that contains only a single imageView. When the switch is flipped add the second imageView to the stackView by calling insertArrangedSubview(:at:) and the stackView will take care of maintaining the centering and any requested spacing. Similarly when the switch is off just call removeArrangedSubview(:) and everything goes back into place.
If you are not on iOS 9 and don't have UIStackView you can just drag an IBOutlet to the view that you want to move's centerX constraint and add to its .constant property and animate layoutIfNeeded to have it slide to the right.
class ViewController: UIViewController{
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var imageViewCenterXConstraint: NSLayoutConstraint!
let padding = CGFloat(10)
#IBAction func tapButton(_ sender: Any) {
imageViewCenterXConstraint.constant = imageView.frame.size.width / 2 + padding
UIView.animate(withDuration: 0.25) { self.view.layoutIfNeeded()}
}
}

Why do my UIImageView coordinates change when I update label value?

I want to move my UIImageView when a certain button is clicked. I have this code:
#IBOutlet weak var counter: UILabel!
#IBOutlet weak var indexCrow: UIImageView!
var crow = 0
#IBAction func plusButton(sender: UIButton) { // Plus Button
indexCrow.frame = CGRect(x: indexCrow.frame.origin.x + 20, y: indexCrow.frame.origin.y, width: indexCrow.frame.size.width, height: indexCrow.frame.size.height)
crow++
counter.text = String(crow)
}
If I remove the line counter.text = String(crow), my image view moves correctly, but my label does not update. If I write counter.text = String(crow) my label updates, but my image view does not move.
What I do wrong?
AutoLayout is running when you update the label and placing your image back to where it started. You have several options to deal with this. Choose one:
Turn off AutoLayout. Most don't choose this option because they want their layouts to work on multiple devices.
Create your imageView programmatically instead of in Interface Builder. If you do this, it won't be subject to AutoLayout and you can move it freely.
Place your imageView using AutoLayout constraints. Add IBOutlets to those constraints and update the constant values in code instead of modifying the frame.
When plusButton is called, store the new frame for your imageView in a property in your ViewController, and then put that frame in place in an override of viewDidLayoutSubviews which happens after AutoLayout runs.

Resources