How to programmatically change constraint constants? - swift

I read related questions(with nearly same titles), but it's not my case. I have a MKMapView and in the bottom of the screen i have ScrollView and ImageView. They are hidden, but when i show them i want to change bottom constraint of my MapView. The problem is, when i update bottom constraint, my MapView ignores it - i made ScrollView and ImageView transparent to check it - and even scrolls up, i don't understand why. My code for updating constant is this:
bt.flyImg.isHidden = true
bt.mapBottom.constant -= bt.flyImg.height
bt.view.layoutIfNeeded()
bt.flyImg.isHidden = false
bt.mapBottom.constant += bt.flyImg.height
bt.view.layoutIfNeeded()
I checked the constraint, it's the constraint i need. I tried to write something like
func layout()
{
self.view.layoutIfNeeded()
}
because i thought it might happen because i tried to update constant from other class, but it didn't help. What am i doing wrong?

The first thing you should do is to check if the constraint is still connected to mapBottom from the Connections Inspector (if you are using Storyboard). If it is and it is still not working, then you can try the alternative approach where you update the MapView's bottom constraint from the constarint's superview.
For this to work you must be able to identify the MapView's bottom constraint by giving it an identifier. To do that, look through the MapView's constraints in the Storyboard's Document Outline, find the bottom constraint, go over to the Size Inspector and give the constraint an identifier, let's say "MapViewBottom".
Now you must look through the MapView's superview constraints, identify the correct one and change the constant.
for constraint in yourMapView.superview!.constraints {
if constraint.identifier == "MapViewBottom" {
constraint.constant = flyImg.isHidden ? -flyImg.height : flyImg.height
}
}
You can then call the view to recalculate constraints if needed
view.layoutIfNeeded()

I think you might have initially decrease the constant of mapBottom to the height of flyImg. Instead of bt.mapBottom.constant -= bt.flyImg.height, change it to its default constant value. If it is pinned to sides of screen than make it
bt.flyImg.isHidden = true
bt.mapBottom.constant = 0 //if default value is 0. Just check it on storyboard.
bt.view.layoutIfNeeded()
bt.flyImg.isHidden = false
bt.mapBottom.constant += bt.flyImg.height
bt.view.layoutIfNeeded()
If it is still not working, Please insert detail source code so that we could figure out what's the matter.

Related

Using Swift, how do I animate the .setPosition() method of an NSSplitView without visually stretching its contents?

I would like to animate the appearance of a NSSplitViewItem using .setPosition() using Swift, Cocoa and storyboards. My app allows a student to enter a natural deduction proof. When it is not correct, an 'advice view' appears on the right. When it is correct, this advice view will disappear.
The code I'm using is the below, where the first function makes the 'advice' appear, and the second makes it disappear:
func showAdviceView() {
// Our window
let windowSize = view.window?.frame.size.width
// A CGFloat proportion currently held as a constant
let adviceViewProportion = BKPrefConstants.adviceWindowSize
// Position is window size minus the proportion, since
// origin is top left
let newPosition = windowSize! - (windowSize! * adviceViewProportion)
NSAnimationContext.runAnimationGroup { context in
context.allowsImplicitAnimation = true
context.duration = 0.75
splitView.animator().setPosition(newPosition, ofDividerAt: 1)
}
}
func hideAdviceView() {
let windowSize = view.window?.frame.size.width
let newPosition = windowSize!
NSAnimationContext.runAnimationGroup{ context in
context.allowsImplicitAnimation = true
context.duration = 0.75
splitView.animator().setPosition(newPosition, ofDividerAt: 1)
}
}
My problem is that the animation action itself is causing the text in the views to stretch, as you can see in this example: Current behaviour
What I really want is the text itself to maintain all proportions and slide gracefully in the same manner that we see when the user themselves moves the separator: Ideal behaviour (but to be achieved programmatically, not manually)
Thus far in my troubleshooting process, I've tried to animate this outside of NSAnimationContext; played with concurrent drawing and autoresizing of subviews in XCode; and looked generally into Cocoa's animation system (though much of what I've read doesn't seem to have direct application here, but I might well be misunderstanding it). I suspect what's going on is that the .animator() proxy object allows only alpha changes and stretches---redrawing so that text alignment is honoured during the animation might be too non-standard. My feeling is that I need to 'trick' the app into treating the animation as though it's being performed by the user, but I'm not sure how to go about that.
Any tips greatly appreciated...
Cheers

Two constraint height conflict UIView

I have UIView, which I have configured programmaticaly.
I tried to change height of MultiSelectsInputView (root view), but instead of changing I've got debug message:
MainApp[5765:768019] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x28204d360 MainApp.MultiSelectsInputView:0x159c08830.height == 300 (active)>",
"<NSLayoutConstraint:0x2820880a0 MainApp.MultiSelectsInputView:0x159c08830.height == 275 (active)>"
)
I'm setting constraints like this
func constraintHeight(constant: CGFloat) {
translatesAutoresizingMaskIntoConstraints = false
heightAnchor.constraint(equalToConstant: constant).isActive = true
}
Why does it conflict with itself?
As per the error you set the height twice - once with constant value 300 and again with value 275, and both of these are active simultaneously. These two constraints conflict with each other and so only one can be active at a time.
Looking at the code it appears that you have called constraintHeight() method twice with different values. If you need to change the height of your view, you should first deactivate older height constraint.
Keep a reference to height constraints, so that you can activate / deactivate them later.
var heightConstraint: NSLayoutConstraint?
heightConstraint = view.heightAnchor.constraint(equalToConstant: constant)
// To activate or deactivate toggle the boolean isActive property
heightConstraint?.isActive = true // Activate height constraint
heightConstraint?.isActive = false // Deactivate height constraint

Auto Layout ImageView Height Anchor conflicting in Collection View and not working properly

I am getting this issue where I have this image View inside a collection view cell and in the cellForRow configure function I am setting the height anchor of the image view depending if the model object contains image or not. If it contains image the height is 200 else it is 1. Now I am getting this error.
The error seems to fix the constraint initially and when the collection view is loaded first time, I am able to view the cells correctly. It is only once I reload the collectionview that I run into the issue of cells not showing the imageview or rather the imageview height is 1 where it should be 200. So '1' height anchor is given priority over 200 one.
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x600001643d40 UIView:0x7fd7fdea2ac0.height == 200 (active)>",
"<NSLayoutConstraint:0x60000165eb70 UIView:0x7fd7fdea2ac0.height == 1 (active)>"
)
I have changed the priorities, but that doesn't seem to work either:
if post.postMedia.isEmpty {
cell.postMedia.heightAnchor.constraint(equalToConstant: 1).isActive = true
cell.postMedia.heightAnchor.constraint(equalToConstant: 1).priority = UILayoutPriority(rawValue: 999)
}else {
cell.postMedia.heightAnchor.constraint(equalToConstant: 200).isActive = true
cell.postMedia.heightAnchor.constraint(equalToConstant: 200).priority = UILayoutPriority(rawValue: 1000)
}
postMedia is the array that contains images. If it is empty the height anchor is 1 and priority is set to 999. and 200 one is given 1000. But that still doesn't work. Is there anything else I can do when two constraints are conflicting one another?
Kindly some help will be appreciated.
I think you are adding new constraints every time a cell is loaded. Cells are reused, so you get multiple colliding constraints for the image height. You should keep a reference to the height constraint in the cell, and just update its constant when you load the cell.
You could make the property optional:
var heightConstraint: NSLayoutContraint?
and create one if it is nil when the cell is loaded, otherwise just update heightConstraint.constant = 200 (or 1).
let height: CGFloat = post.postMedia.isEmpty ? 1 : 200
if cell.heightConstraint == nil {
cell.heightConstraint = cell.postMedia.heightAnchor.constraint(equalToConstant: height)
cell.heightConstraint?.isActive = true
} else {
cell.heightConstraint.constant = height
}

Added subview is positioned slightly off - how can I get it back into place?

UPDATE: Solved! While the contentMode for pianoNoteDisplayed and piano_background were indeed the same, apparently this wasn't true for the added subviews. I simply added the line subview.contentMode = superview.contentMode to the function vdmzz suggested, and now everything looks right on all 4 screen sizes.
There are two image views: one called "piano_background" holds a background image (a piano keyboard) and the other will be used to display highlighted notes. The second is constrained to the first:
(the width constraint is probably unnecessary, because the leading and trailing constraints are already set, right?)
To display multiple highlighted keys, I am programmatically adding subviews to the piano_note view and activating the NSLayoutConstraints to get it into place (otherwise it shows up way out of position) like so:
pianoNoteDisplayed.image = nil
if !notesAlreadyAttempted.contains(currentUserAnswer) {
let wrongNoteImageName = "large_\(currentUserAnswer)_wrong"
let wrongNoteImage = UIImage(named: wrongNoteImageName)
let wrongNoteImageView = UIImageView(image: wrongNoteImage!)
wrongNoteImageView.translatesAutoresizingMaskIntoConstraints = false
pianoNoteDisplayed.addSubview(wrongNoteImageView)
NSLayoutConstraint.activate([
wrongNoteImageView.widthAnchor.constraint(equalToConstant: pianoNoteDisplayed!.frame.width),
wrongNoteImageView.heightAnchor.constraint(equalToConstant: pianoNoteDisplayed!.frame.height)
])
}
notesAlreadyAttempted.append(currentUserAnswer)
}
The issue is that the subview is displayed slightly off, and I can't seem to figure out why:
(as you can see, the highlight looks slightly compressed vertically.. the top lands correctly, but the bottom doesn't reach far enough by about 5px)
I have tried centering and constraining the subview in multiple ways, using suggestions from about 5 different answers on stack, and a few other articles I found. The images I am using (the piano background and the overlaying note highlight subview) are identical sizes. I have tried adding more or fewer constraints in the interface builder, and I have tried adding subviews to the original piano_background view instead of the second pianoNoteDisplayed view - same result. Using the pianoNoteDisplayed view itself to display the highlighted note works fine by the way:
And these are displayed using the usual .image method:
pianoNoteDisplayed.image = UIImage(named: "large_\(currentCorrectAnswer)_right")
Any suggestions for how to troubleshoot the issue further?
First of all, as far as I understood pianoNoteDisplayed doesn't need to be an UIImageView.
Secondly, if you align piano_background and pianoNoteDisplayed by top, leading, trailing and bottom edges, one will be exactly on top of other. Or you could set them equal height, width and center positions.
The problem with your current set of constraints is that piano_background's Y position is determined by Safe Area and therefore might defer from pianoNoteDisplayed's Y position.
Try using this function:
func addSameSize(subview: UIView, onTopOf superview: UIView) {
superview.addSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = false
subview.centerXAnchor.constraint(equalTo: superview.centerXAnchor).isActive = true
subview.centerYAnchor.constraint(equalTo: superview.centerYAnchor).isActive = true
subview.widthAnchor.constraint(equalTo: superview.widthAnchor).isActive = true
subview.heightAnchor.constraint(equalTo: superview.heightAnchor).isActive = true
subview.contentMode = superview.contentMode
}
E.g.
addSameSize(subview: wrongNoteImageView, onTopOf: pianoNoteDisplayed)
It will add your image views exactly aligned on top of pianoNoteDisplayed view

iOS Autolayout - How to set two different distances between views, depends on the screen height

I know I'm missing something, because this has to be something easy to achieve.
My problem is that I have in my "loading screen" (the one that appears right after the splash) an UIImageView with two different images for 3.5" and 4" size screen. In a certain place of that images, I put one UIActivityIndicator, to tell the user that the app is loading something in the background. That place is not the same for both images, because one of them is obviously higher that the other, so I want to set an autolayout constraint that allows me to put that activity indicator at different heights, depends on if the app is running in an iPhone 5 or not.
Without Autolayout, I'd set the frame.origin.y of the view to 300 (for example), and then in the viewDidLoad method of the ViewController, I'd ask if the app is running in an iPhone 5, so I'd change the value to, for example, 350. I have no idea how to do this using Autolayout and I think it has to be pretty simple.
You can create an NSLayoutConstraint outlet on your view controller and connect the outlet to the activity indicator's Y constraint in your xib or storyboard. Then, add an updateViewContraints method to your view controller and update the constraint's constant according to the screen size.
Here's an example of updateViewConstraints:
- (void)updateViewConstraints {
[super updateViewConstraints];
self.activityIndicatorYConstraint.constant =
[UIScreen mainScreen].bounds.size.height > 480.0f ? 200 : 100;
}
Of course you will want to put in your appropriate values instead of 200 and 100. You might want to define some named constants. Also, don't forget to call [super updateViewConstraints].
The problem of #Rob answer's is you should do a lot of code for each constraint.
So to resolve that, just add ConstraintLayout class to your code and modify constraint constant value for the device that you want in the IB :
//
// LayoutConstraint.swift
// MyConstraintLayout
//
// Created by Hamza Ghazouani on 19/05/2016.
// Copyright © 2016 Hamza Ghazouani. All rights reserved.
//
import UIKit
#IBDesignable
class LayoutConstraint: NSLayoutConstraint {
#IBInspectable
var 📱3¨5_insh: CGFloat = 0 {
didSet {
if UIScreen.main.bounds.maxY == 480 {
constant = 📱3¨5_insh
}
}
}
#IBInspectable
var 📱4¨0_insh: CGFloat = 0 {
didSet {
if UIScreen.main.bounds.maxY == 568 {
constant = 📱4¨0_insh
}
}
}
#IBInspectable
var 📱4¨7_insh: CGFloat = 0 {
didSet {
if UIScreen.main.bounds.maxY == 667 {
constant = 📱4¨7_insh
}
}
}
#IBInspectable
var 📱5¨5_insh: CGFloat = 0 {
didSet {
if UIScreen.main.bounds.maxY == 736 {
constant = 📱5¨5_insh
}
}
}
}
Don't forgot to inherit your class constraint from ConstraintLayout
I will add the objective-c version soon
The basic tool in Auto Layout to manage UI objects' position is the Constraints. A constraint describes a geometric relationship between two views. For example, you might have a constraint that says:
“The right edge of progress bar is connected to the left edge of a lable 40 points of empty space between them.”
This means using AutoLayout you can't do conditional position setting based on UIDevice's mode, rather you can create a view layout which modifies itself if eg. the app runs on 3.5' full screen (IPhone4) or 4' full screen (IPhone5) based on the constraints.
So options for your problem using Constraints:
1) find a view on your layout which can be used to create a constraint to position the progressbar relatively. (select the view and the progressbar using CMD button, then use Editor/Pin/Vertical Spacing menu item to create a vertical constraint between the 2 objects)
2) create an absolute constraint to stick the progressbar's position to screen edge (keeping space) or centrally
I found helpful this tutorial about AutoLayout which might be beneficial for you also:
http://www.raywenderlich.com/20881/beginning-auto-layout-part-1-of-2
Pls note: autolayout only works from IOS 6.
The new way, Without writing a single line!
No need to write device based conditions like these :-
if device == iPhoneSE {
constant = 44
} else if device == iPhone6 {
constant = 52
}
I created a library Layout Helper so now you can update constraint for each device without writing a single line of code.
Step 1
Assign the NSLayoutHelper to your constraint
Step 2
Update the constraint for the device you want
Step 3
Run the app and see the MAGIC
I generally always try to stay in Interface Builder for setting up constraints. Diving in code to have more control is usually useful if you have completely different layouts on iPhone 4 and 6 for example.
As mentioned before, you can't have conditionals in Interface Builder, that's when linking a constraint to your view controller really comes handy.
Here's a short explanation on 3 approaches to solve Auto Layout issues for different screen sizes: http://candycode.io/how-to-set-up-different-auto-layout-constraints-for-different-screen-sizes/