having trouble changing an auto layout constraint - ios

I want to both resize and relocate an auto layout constraint when there are characters in a textview. Yes, I am using a textview delegate. Yes, the view does layout properly when the view loads. The problem arises when I attempt to resize the view... I can move it how I want (centerX), however when I change the width property the view animates away and disappears...
Here's the code...
paddingView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5).isActive = true
NSLayoutConstraint(item: paddingView, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 0.75, constant: 0).isActive = true
paddingView.heightAnchor.constraint(equalToConstant: 6.0).isActive = true
bottomConstraint = paddingView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0)
bottomConstraint?.isActive = true
Here's the second part
func textViewDidChange(_ textView: UITextView) {
let currentString: String = textView.text!
let length: Int = (currentString.characters.count )
if length > 0 {
NSLayoutConstraint(item: paddingView, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true
paddingView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.75).isActive = true
}else{
}
UIView.animate(withDuration: 0.2, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
self.view.layoutIfNeeded()
}, completion: { (completed) in
})
}
Could it be because I am using anchors and NSLayoutConstraints together?

For resizing:You can increase and decrease the size of the textview dynamically by not setting fixed constraints to super view and playing around with compression hugging and resistance priorities.
For relocating: Enable and disable constraints at various places in textview delegate

Related

IOS how to completely remove a View

I want to hide a view completely. I am new to ios. But I have great working experience in android.
So in android We can set visibility to gone. and it completely removes the view from layout. Same I want to do in IOS. here is the layout/design example
View 1
View 2 ( it is the one I want to hide and show)
View 3
now when I want to hide the view 2 I want the space of view 2 also vanish from screen and View 1 and View 3 must stick together . and when view 2 is set to visible then it must display in sequence. i.e View 1,2,3
Right now what I am doing is setting view2.ishidden = true but its not working in the way I want.
Please tell me what is equivalent to view.gone of android in IOS. ???
There are a few ways in achieving this but what you are missing here is some key information on how your layout is being set.
I will assume that the 3 views you are having are vertically aligned, are one next to each other and have equal width.
Programmatically what we are looking at from horizontal perspective this is done:
let firstLeading = NSLayoutConstraint(item: view1, attribute: .leading, relatedBy: .equal, toItem: parent, attribute: .leading, multiplier: 1.0, constant: 0.0)
let betweenSecondAndFirst = NSLayoutConstraint(item: view2, attribute: .leading, relatedBy: .equal, toItem: view1, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let secondEqualWidth = NSLayoutConstraint(item: view2, attribute: .width, relatedBy: .equal, toItem: view1, attribute: .width, multiplier: 1.0, constant: 0.0)
let betweenThirdAndSecond = NSLayoutConstraint(item: view3, attribute: .leading, relatedBy: .equal, toItem: view2, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let lastEqualWidth = NSLayoutConstraint(item: view3, attribute: .width, relatedBy: .equal, toItem: view1, attribute: .width, multiplier: 1.0, constant: 0.0) // Note to view1 to make things easier
let lastTrailing = NSLayoutConstraint(item: view3, attribute: .trailing, relatedBy: .equal, toItem: parent, attribute: .trailing, multiplier: 1.0, constant: 0.0)
And when the second is being skipped we may simply disable betweenThirdAndSecond and add betweenThirdAndFirst as:
let betweenThirdAndFirst = NSLayoutConstraint(item: view3, attribute: .leading, relatedBy: .equal, toItem: view1, attribute: .trailing, multiplier: 1.0, constant: 0.0)
You can play with properties on constraints to enable or disable them. Or you can simply use priorities and toggle them for instance from 900 to 100. You can actually setup all of these constraints in storyboard and then drag the 2 as outlets into your code. Then simply have:
func setMiddleViewShown(_ shown: Bool) {
UIView.animate(withDuration: 0.3) {
self.betweenThirdAndSecond.priority = UILayoutPriority(rawValue: shown ? 900.0 : 100.0)
self.betweenThirdAndFirst.priority = UILayoutPriority(rawValue: shown ? 100.0 : 900.0)
self.view2.alpha = shown ? 1.0 : 0.0
parent.layoutIfNeeded() // parent is most likely "self.view"
}
}
This way is probably best from what you can control (mostly animations). But you may as well use UIStackView which has methods to insert or remove views. UICollectionView should work as well. Or you know.. just do it all programmatically, ignore constraints and simply set frame for each of the views.
A minimum I can think of in using UIStackView is the following and it works:
class ViewController: UIViewController {
let views: [UIView] = [
{ let view = UIView(); view.backgroundColor = .red; return view; }(),
{ let view = UIView(); view.backgroundColor = .green; return view; }(),
{ let view = UIView(); view.backgroundColor = .blue; return view; }()
]
lazy var stackView: UIStackView = {
let stackView = UIStackView(frame: CGRect(x: 50.0, y: 100.0, width: 300.0, height: 200.0))
self.view.addSubview(stackView)
stackView.backgroundColor = .black
stackView.distribution = .fillEqually
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
stackView.addArrangedSubview(views[0])
stackView.addArrangedSubview(views[1])
stackView.addArrangedSubview(views[2])
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
UIView.animate(withDuration: 0.3) {
if self.stackView.arrangedSubviews.count == 3 {
self.views[1].alpha = 0.0
self.stackView.removeArrangedSubview(self.views[1])
} else {
self.views[1].alpha = 1.0
self.stackView.insertArrangedSubview(self.views[1], at: 1)
}
self.stackView.layoutIfNeeded()
}
}
}

How to make a view scroll against a scrollview animation?

So I have a UIView and I want to have it animate a translation in the opposite direction of the scrollviews scroll animation. How would I go about doing this? Of course this should be depend on the scrollview.contentOffset I think anyway. So if you scroll the other way it goes back to it's place. So the translation is dependent on how far the user scrolls. I am trying to use the following code. Also note that the UIView I currently do not have as a child of the scrollview. It is suppose to stay on screen at all time, but change positions.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x >= scrollView.contentSize.width {
UIView.animate(withDuration: 2.0, animations: {
self.view.layoutIfNeeded()
})
}
A simple solution would be:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let percentage = scrollView.contentOffset.x / scrollView.contentSize.width
self.box.frame.origin.x = UIScreen.main.bounds.width * percentage
}
Just calculate the percentage of the contentSize that you have scrolled and set the X position to the same percentage of the screen width.
It could do with some improvements to make sure the view doesn't go beyond the bounds of the screen in certain conditions. So you may need to factor in the width of the box in the calculations as an enhancement.
Here is the code I used for testing, The background image is a 4K wallpaper image.
class ViewController: UIViewController {
var box = UIView()
var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
self.scrollView = UIScrollView()
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.scrollView.delegate = self
self.view.addSubview(self.scrollView)
NSLayoutConstraint(item: scrollView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: scrollView, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: scrollView, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: scrollView, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1.0, constant: 0).isActive = true
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = UIImage(named: "background")
self.scrollView.addSubview(imageView)
NSLayoutConstraint(item: imageView, attribute: .top, relatedBy: .equal, toItem: scrollView, attribute: .top, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: imageView, attribute: .left, relatedBy: .equal, toItem: scrollView, attribute: .left, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: imageView, attribute: .right, relatedBy: .equal, toItem: scrollView, attribute: .right, multiplier: 1.0, constant: 0).isActive = true
NSLayoutConstraint(item: imageView, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1.0, constant: 0).isActive = true
box.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 110, width: 100, height: 100)
box.backgroundColor = .black
self.view.addSubview(box)
}
}
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let percentage = scrollView.contentOffset.x / scrollView.contentSize.width
let xPosition = UIScreen.main.bounds.width * percentage
self.box.frame.origin.x = xPosition
}
}

Position subviews on superview based on subview count

Subviews need to be placed from the center of super view based on subview count.
If subview count is odd, the middle view will be center to the super view and remaining will be placed with respect to it with item spacing.
If subview count is even, then subviews are to be placed with offset.
Is there any generic solution to solve this without too many conditions and calculations using autolayout?
All subviews are of same size
Solution 1: UIStackView
let totalSubviews = 4
let stackView = UIStackView()
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .Horizontal
stackView.distribution = .EqualSpacing
stackView.alignment = .Center
stackView.spacing = 10
stackView.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor, constant: 0).active = true
stackView.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor, constant: 0).active = true
stackView.backgroundColor = UIColor.greenColor()
for _ in 0 ..< totalSubviews {
let subview = UIView()
stackView.addArrangedSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = false
subview.backgroundColor = UIColor.redColor()
subview.heightAnchor.constraintEqualToConstant(50).active = true
subview.widthAnchor.constraintEqualToConstant(50).active = true
}
Solution 2: AutoLayout
You can do this generically using AutoLayout.
totalSubviews = 4
totalSubviews = 5
Code
let totalSubviews = 4
let spacing = CGFloat(20)
var subviews = [UIView]()
var previousSubview: UIView?
for i in 0 ..< totalSubviews {
let subview = UIView()
subviews.append(subview)
view.addSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = false
subview.backgroundColor = UIColor.redColor()
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: spacing))
if i == 0 {
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Left, relatedBy: .Equal, toItem: view, attribute: .Left, multiplier: 1, constant: spacing))
}
else if i < totalSubviews {
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Left, relatedBy: .Equal, toItem: previousSubview, attribute: .Right, multiplier: 1, constant: spacing))
}
if i == totalSubviews - 1 {
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1, constant: -spacing))
}
if i != 0 {
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Width, relatedBy: .Equal, toItem: previousSubview!, attribute: .Width, multiplier: 1, constant: 0))
}
view.addConstraint(NSLayoutConstraint(item: subview, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: 100))
previousSubview = subview
}
You will have to take 1 more view inside main view which will include your odd or even number of subviews. Then you need to set Equal width and Equal height constraints between them. So as per your number of subviews and screen size, they all will automatically be positioned.

Add Constraint to navigationBar Programmatically Swift

I'm trying to add constraint to navigation bar, I have UIImageView, which has width, height and is centered horizontally, I want to add vertical space between UIImage and navigationBar to 0, I'm trying this for like 1 hour and couldn't figure out how, i tried adding constraint to UIView, and added constant of navbarHeight + statusBarHeight, and it worked, but I want to make relationship between imageview and navbar
let verticalSpace = NSLayoutConstraint(item: image, attribute: .Top, relatedBy: .Equal, toItem: self.view, attribute: .Top, multiplier: 1, constant: 0)
view.addConstraint(verticalSpace) // this works
try with topLayoutGuide
let verticalSpace = NSLayoutConstraint(item: image,
attribute: .Top,
relatedBy: .Equal,
toItem: self.topLayoutGuide,
attribute: .Bottom,
multiplier: 1, constant: 0)
The above constraint explanation:
simply its called: vertical space between image.Top & self.topLayoutGuide.Bottom = 0
that means Top constraint of image view attached with a Bottom attribute of topLayoutGuide with constant 0.
You can use anchors as well to make this possible for iOS 10+
if #available(iOS 11.0, *) {
image.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
} else {
image.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
}
llkenny's answer for iOS 11.0+ :
image.topAnchor.constraint(equalTo:
view.safeAreaLayoutGuide.topAnchor).isActive = true
With anchors:
image.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor).isActive = true
In storyboard. Two constraints:
The first:
The second:
Result:
code:
func mainCollectionViewConstraint() {
NSLayoutConstraint.activate([
mainCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
mainCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mainCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
mainCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}

Autolayout Resize UIView Not Working

I am attempting to get a UIView courseView to autolayout. I would like to have the proportions of the UIView remain and fill up until the outermost edges are 15 point from the edge of the superview.
For some reason courseView fills the entire superview (minus the 15 points) and does not resize to fit. So some of it does not show and is cut off.
self.view.addSubview(courseView!)
let aspectConstraint = NSLayoutConstraint(item: courseView,
attribute: .Height,
relatedBy: .Equal,
toItem: courseView,
attribute: .Width,
multiplier: courseView.frame.size.height / courseView.frame.size.width,
constant: 0.0)
aspectConstraint.active = true
let topConstraint = courseView.topAnchor.constraintGreaterThanOrEqualToAnchor(topLayoutGuide.bottomAnchor, constant: 15)
topConstraint.active = true
let leadingConstraint = courseView.leadingAnchor.constraintLessThanOrEqualToAnchor(view.leadingAnchor, constant: 15)
leadingConstraint.active = true
let trailingConstraint = courseView.trailingAnchor.constraintGreaterThanOrEqualToAnchor(view.trailingAnchor, constant: -15)
trailingConstraint.active = true
let bottomConstraint = courseView.bottomAnchor.constraintLessThanOrEqualToAnchor(view.bottomAnchor, constant: -15)
bottomConstraint.active = true
Any ideas? Thanks!
Just disable translatesAutoresizingMaskIntoConstraints before adding the constraints and it should work just fine.
courseView.translatesAutoresizingMaskIntoConstraints = false;
And by the way, you do not need the aspectConstraint because that will most probably break the constratints (it did for me, when I tried).

Resources