Is there a way to change view’s centerXAnchor constraint to another object (with animation) by pressing a button while the app is running?
Thank's for all of the answers!
First, you need to create an IBOutlet for your constraint from the storyboard, something similar to this.
#IBOutlet weak var centerXConstraint: NSLayoutConstraint!
And when the button is pressed, you should change the constraint value and update the view layout.
centerXConstraint.constant = setYourValueHere
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
If you want to add constraints programmatically, then remove #IBOutlet weak.
var centerXConstraint: NSLayoutConstraint!
Assign your view's center X anchor to it.
centerXConstraint = yourView.centerXAnchor.constraint(equalTo: super.view.centerXAnchor, constant: 0)
centerXConstraint.isActive = true
after that, you can change it as described above.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var centerXConstraintOutlet: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func buttonClickAction(_ sender: UIButton) {
animateViewCenterXConstraint()
}
func animateViewCenterXConstraint() {
UIView.animate(withDuration: 0.3) {
self.centerXConstraintOutlet.constant = 30
self.view.layoutIfNeeded()
}
}
}
1) Create view’s centerXAnchor constraints related to both the objects.
2) Take outlets of both the constraints.
3) Activate and de-activate them on click of the button as you wish.
Declare variable for centerXAnchor constraint and assign value on button action. You can try this:
var constraintOfCenterXAnchor: NSLayoutConstraint = NSLayoutConstraint()
in viewDidLoad() set constraintOfCenterXAnchor.constant = 0
When button pressed, change constraint value and update view
constraintOfCenterXAnchor.constant = 30
UIView.animate(withDuration: 0.3, animations: {
self.view.layoutIfNeeded()
})
Related
I am trying to make an app that utilizes some search feature and I am trying to make it so that after the search button is pressed, a view (which contains the search results) moves up from the bottom of the superview and replaces the search view. On the storyboard, the resultsView (of type UIView) is constrained so that its top is equal to the superview's bottom. After the search button is pressed, I would like to animate the view to move up and replace the view already at the bottom of the superview. The problem is, in the viewcontroller's class, when I call the resultsView, the animateWithDuration(NSTimeInterval) that is supposed to be associated with the UIView class is not appearing for me. May this be because the view is already constrained in place? Here is the code, simplified for this post:
import UIKit
import MapKit
class MapViewController: UIViewController, CLLocationManagerDelegate,
MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var slider: UISlider!
#IBOutlet weak var distanceLabel: UILabel!
#IBOutlet weak var searchButton: UIButton!
#IBOutlet weak var searchView: UIView!
#IBOutlet weak var resultView: UIView!
#IBOutlet weak var resultNameLabel: UILabel!
#IBOutlet weak var resultDistanceLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
self.resultView.isHidden = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func sliderAdjusted(_ sender: Any) {
let int = Int(slider.value)
switch int {
case 1:
distanceLabel.text = "1 mile"
default:
distanceLabel.text = "\(int) miles"
}
}
#IBAction func searchButtonPressed(_ sender: Any) {
/*This is where, after calling a search function which is omitted
from this example, I would like to make the resultsView not
hidden and then animate it to sort of eclipse the search view*/
self.resultView.isHidden = false
self.resultView.animate(withDuration: NSTimeInterval)
/*The above line of code is not actually appearing for me.
After typing "self.resultView." the animate function is not being
given to me as an option for my UIView*/
}
}
I will also attach some images of the view controller so you can sort of get the idea. The results view is not visible in this image because its top is constrained to the superview's bottom, thus it is just out of the visible representation of its superview.
The first image is the view controller with the searchView highlighted. This is the view that I would like to be eclipsed by my resultView after the searchButton is pressed.
The second image is the same view controller with the resultView highlighted. As you can see, its top is constrained to be equal to the superview's bottom. This is the view that I would like to animate upwards into the superview and eclipse the searchView after the search button is pressed.
The methods for all the animate family are all class methods. Which means you call them on the class object not an instance.
You are trying to call
class func animate(withDuration: TimeInterval, animations: () -> Void)
so your code needs to look like
UIView.animate(withDuration: 0.5) {
//the things you want to animate
//will animate with 0.5 seconds duration
}
In the particular case it looks like you are trying to animate the height of resultView so you need an IBOutlet to that constraint. You could call it resultViewHeight.
UIView.animate(withDuration: 0.5) {
self.resultViewHeight.constant = theDesiredHeight
self.layoutIfNeeded()
}
Calling layoutIfNeeded() within the closure is the secret sauce to animating auto layout. Without that the animation will just jump to the and point.
I have a UIView and I set the constraints using Xcode Interface Builder.
Now I need to update that UIView instance's height constant programmatically.
There is a function that goes like myUIView.updateConstraints(), but I don't know how to use it.
Select the height constraint from the Interface builder and take an outlet of it. So, when you want to change the height of the view you can use the below code.
yourHeightConstraintOutlet.constant = someValue
yourView.layoutIfNeeded()
Method updateConstraints() is an instance method of UIView. It is helpful when you are setting the constraints programmatically. It updates constraints for the view. For more detail click here.
If you have a view with multiple constrains, a much easier way without having to create multiple outlets would be:
In interface builder, give each constraint you wish to modify an identifier:
Then in code you can modify multiple constraints like so:
for constraint in self.view.constraints {
if constraint.identifier == "myConstraint" {
constraint.constant = 50
}
}
myView.layoutIfNeeded()
You can give multiple constrains the same identifier thus allowing you to group together constrains and modify all at once.
Change HeightConstraint and WidthConstraint Without creating IBOutlet.
Note: Assign height or width constraint in Storyboard or XIB file. after fetching this Constraint using this extension.
You can use this extension to fetch a height and width Constraint:
extension UIView {
var heightConstraint: NSLayoutConstraint? {
get {
return constraints.first(where: {
$0.firstAttribute == .height && $0.relation == .equal
})
}
set { setNeedsLayout() }
}
var widthConstraint: NSLayoutConstraint? {
get {
return constraints.first(where: {
$0.firstAttribute == .width && $0.relation == .equal
})
}
set { setNeedsLayout() }
}
}
You can use:
yourView.heightConstraint?.constant = newValue
Drag the constraint into your VC as an IBOutlet. Then you can change its associated value (and other properties; check the documentation):
#IBOutlet myConstraint : NSLayoutConstraint!
#IBOutlet myView : UIView!
func updateConstraints() {
// You should handle UI updates on the main queue, whenever possible
DispatchQueue.main.async {
self.myConstraint.constant = 10
self.myView.layoutIfNeeded()
}
}
You can update your constraint with a smooth animation if you want, see the chunk of code below:
heightOrWidthConstraint.constant = 100
UIView.animate(withDuration: animateTime, animations:{
self.view.layoutIfNeeded()
})
To update a layout constraint you only need to update the constant property and call layoutIfNeeded after.
myConstraint.constant = newValue
myView.layoutIfNeeded()
If the above method does not work then make sure you update it in Dispatch.main.async{} block. You do not need to call layoutIfNeeded() method then.
First connect the Height constraint in to our viewcontroller for creating IBOutlet like the below code shown
#IBOutlet weak var select_dateHeight: NSLayoutConstraint!
then put the below code in view did load or inside any actions
self.select_dateHeight.constant = 0 // we can change the height value
if it is inside a button click
#IBAction func Feedback_button(_ sender: Any) {
self.select_dateHeight.constant = 0
}
Create an IBOutlet of NSLayoutConstraint of yourView and update the constant value accordingly the condition specifies.
//Connect them from Interface
#IBOutlet viewHeight: NSLayoutConstraint!
#IBOutlet view: UIView!
private func updateViewHeight(height:Int){
guard let aView = view, aViewHeight = viewHeight else{
return
}
aViewHeight.constant = height
aView.layoutIfNeeded()
}
If you update your constraints with animation then add layout subview in UIView.animate...
for example,
#IBOutlet viewHeight: NSLayoutConstraint!
#IBOutlet view: UIView!
private func updateViewHeight(height:Int){
guard let aView = view, aViewHeight = viewHeight else{
return
}
aViewHeight.constant = height
UIView.animateWithDuration(0.5, delay: 0) { [self] in
aView.layoutIfNeeded()
}
}
I have a UITableView with a list of items that can be filtered. When the user chooses a filter, a UIView is exposed using a height NSLayoutConstraint that changes from 0 to 40 pixels:
The circle with the X is the clear button that clears the filter and then closes the UIView by changing the height constraint back to 0.
The problem is that when the UIView closes, that clear button doesn't completely go away:
Here's the relevant code:
class LiftLogViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
let coreDataStack = CoreDataStack()
var liftEvents = [LiftEvent]()
//MARK: IB outlets
#IBOutlet var tableView: UITableView!
#IBOutlet weak var navItem: UINavigationItem!
#IBOutlet weak var filterViewHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var clearFilterButton: UIImageView!
#IBOutlet weak var selectedFilter: UILabel!
#IBOutlet weak var clearButtonHeightConstraint: NSLayoutConstraint!
#IBOutlet weak var clearButtonView: UIImageView!
var isFilterViewOpen = false
override func viewDidLoad() {
let doneButton = UIBarButtonItem(title: "Done", style: .Plain, target: self, action: #selector(self.dismissLog(_:)))
let filterImage = UIImage(named: "filter_icon")
let filterButton = UIBarButtonItem(image: filterImage, style: .Plain, target: self, action: #selector(self.actionFilter))
self.navItem.rightBarButtonItems = [doneButton, filterButton]
let buttonTap = UITapGestureRecognizer(target: self, action: #selector(self.clearFilter))
clearFilterButton.addGestureRecognizer(buttonTap)
filterViewHeightConstraint.constant = 0.0
clearButtonHeightConstraint.constant = 0.0
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
let filterPredicate: NSPredicate?
if let logFilter = UserDefaultsManager.sharedInstance.logFilter?.rawValue {
filterPredicate = NSPredicate(format: "lift.liftName = [c] %#", logFilter)
print("viewWillAppear thinks the filter is \(logFilter)")
} else {
filterPredicate = nil
}
reloadData(filterPredicate)
let currentFilter = getCurrentLogFilter()
if currentFilter != nil {
selectedFilter.text = "Filtered by \(currentFilter!)"
isFilterViewOpen = true
clearButtonView.hidden = isFilterViewOpen ? false : true
} else {
selectedFilter.text = nil
}
super.viewWillAppear(animated)
}
override func viewDidAppear(animated: Bool) {
filterViewHeightConstraint.constant = isFilterViewOpen ? 40.0 : 0.0
clearButtonHeightConstraint.constant = isFilterViewOpen ? 21.0 : 0.0
clearButtonView.hidden = isFilterViewOpen ? false : true
UIView.animateWithDuration(0.33, delay: 0, options: [.CurveEaseOut], animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}
func clearFilter() {
UserDefaultsManager.sharedInstance.logFilter = nil
isFilterViewOpen = !isFilterViewOpen
UIView.animateWithDuration(0.33, delay: 0, options: [.CurveEaseOut], animations: {
self.view.layoutIfNeeded()
}, completion: nil)
selectedFilter.text = nil
reloadData()
}
You can see I've tried both setting the .hidden value on the UIView that holds the clear button to true and I've tried changing the height constraint to 0.0 but neither of those make it gone completely.
Searching for a while hasn't produced any answers. Can anybody point out what's wrong with what I'm doing? Thanks.
It seems like the hair line color on the navigation bar may be clear resulting in the button still being slightly visible. Maybe change that and see if it covers the button. You can do so like this:
let view = UIView()
view.backgroundColor = UIColor.grayColor()
self.navigationController!.navigationBar.hairlineImageViewInNavigationBar(view)
As Dustin Spengler mentioned, the transparent hairline image view in the navigation bar causes the filter button to be slightly visible beneath it. As a workaround, you can hide the filter view (filterView.hidden = true) when constraint constant is set to 0.0 and unhide the filter view (filterView.hidden = false) when constraint constant is set to 40.0.
Thank you guys for your suggestions. It turns out that the solution was a little different, but your suggestions got me to poke around some more in that area and find the solution.
I neglected to mention that this was a view controller that I add a UIStackView that wrapped my UIView (the grey "filtered by" UIView) and the UITableView:
The problem was that the constraint I created from the top of the UIStackView to the top of its parent view was just slightly too big, pushing the top of the UIStackView just far enough to expose that little sliver. I reduced the Y distance to 63 and it's now perfect:
Thanks again for trying to help.
demo image
When I update a number to label will make the view get back to original position.
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBOutlet var aview: UIView!
var number = 0
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func plus(sender: UIButton) {
number++
label.text = "number:\(number)"
}
#IBAction func move(sender: AnyObject) {
aview.frame.origin.y -= 20
}
}
I couldn't find the answer on web, please help me to fix this problem.Thank you very much!
Because your xib or Storyboard you set use Autolayout:
So if you don't set constrain system will auto generate it. When you change frame by set frame it effect but when you access to it. It will auto back to old position.
if you don't want it happen. You set in viewDidLoad:
self.view.translatesAutoresizingMaskIntoConstraints = false
Issue is probably related to a constraint reloading when the label reloads.
Instead of setting aview.frame.origin.y -= 20 you should make an outlet to the constraint holding the y position of your aView and then update the constant of that constraint outlet instead.
I want to move a 40, 40 pixel square from the top to the bottom, then repeat that action over a period of 3 seconds. Where would I put the entire thing in the viewcontroller? There is no start button so would I put in in the viewdidload?
So far I have this
UIView.animateWithDuration(0.2, animations: {
}, completion: {
})
As somewhere to start. Would I set the image to equal UIView? What should It look like? the image is called mrock.
I tried using blank.moveToPoint(CGPoint(x: 0,y: 0)) in the animations part, but dont I need A UIBezierPath thing?
As #picciano stated you should have an IBOutlet to an UIImageView if you are using storyboards. So you in your view controller you can try something like this:
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//'img' is a picture imported in your project
self.imageView.image = UIImage(named: "img")
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let newPos: CGFloat = 300.0
UIView.animateWithDuration(1.0, animations: { () -> Void in
self.imageView.frame = CGRectMake(0, newPos, CGFloat(self.imageView.bounds.size.width), CGFloat(self.imageView.bounds.size.height))
})
}
First, consider putting the animation in viewWillAppear, then disable it if needed in viewDidDisappear.
Second, if you're using auto layout, you will probably need to create an IBOutlet for the relevant constraint and animate the constant property of the constraint rather than the position of the view object directly.