Setting frame value for all items in IBOutletCollection - ios

I created an IBOutletCollection with some UIElements (Label, Button, TextField) that i would like to move on a special event.
thats what I have so far:
#IBAction func moveDown(sender: UIButton) {
UIView.animateWithDuration(0.4, animations: {
for subview in self.moveOutViews {
var offset = CGFloat(700);
var newY = subview.frame.origin.y + offset;
subview.frame = CGRectMake(subview.frame.origin.x, newY, subview.frame.size.width, subview.frame.size.height);
}
}, completion: {
(value: Bool) in
UIView.commitAnimations();
});
}
now my problem is, when the Animation is completed, and i want to interact with the UIElements (TextField) on the new position, all views 'jump' back to the origin position.
maby it's a problem that the 'sender' is part of the IBOutletCollection?
It's an iPad for iOS 8.3 app and i am using Swift.
Edit: I made a workaround with an UIScrollView. But I would like to know why the first idea isn't working.

Since we don't know how your view is setup I will have to guess that it is one of two things:
Like #Paulw11 suggested in his comment. You are editing the frames of views that are held by constraints. When the view is laid out again all views snap back to positions determined by the constraints. Event though you say that you are not using constraints it is possible that translatesAutoresizingMaskIntoConstraints is enabled for your views or that your xib/storyboard is set to use constraints and so constraints are automatically generated for you. One way to check this is to just log the constraints property of a subview.
You have code somewhere else in your app that is changing the position of your views and it is inadvertently being called.

Related

iOS - center.y doesn't move in view

I have a UIViewController with a separated UIView inside it. This view has some textfields. If the user taps on one of the fields, the view will automatically fit his center.y. In the end the textfield which the user tapped will be in the middle of the screen. But in my case the view doesn't move. In the simulator it worked great, but on a real phone nothing happens. Only the output center.y position changes. I fixed the UIView with constraints.
Here is the code for the tap action.
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.layer.borderWidth = 0
textField.clearsOnBeginEditing = true
textField.text = ""
print("Began")
let textFieldCoInView = textField.center
let coInSelf = self.dataView.convert(textFieldCoInView, to: self.view)
print(coInSelf)
print(self.view.center.y)
if(coInSelf.y > self.view.center.y) {
dataViewOriginY = dataView.center.y
print("Works")
self.dataView.center.y = self.dataView.center.y - (coInSelf.y - self.view.center.y)
print(self.dataView.center.y)
}
}
"I fixed the UIView with constraints."
If the view you're trying to move has constraints on it, autolayout is probably fighting you. to test that theory try turning off constraints for the view you're moving.
If that's it, you'll probably need to do something like make outlets to the constraints and change the values in your code (or not use constraints).
In any case, I think you'll find that autolayout is fighting you and keeping the view where its constraints say it belongs.
Please check the constraint you have set for the UIView . and also check the console for errors. Constraints can stop the code to change view. Maybe turn off the constraint and check if the code works.

Changing the constant of a NSLayoutConstraint

Background
I'm trying to do a fairly standard NSLayoutConstraint constant update, with both an animated and non-animated option. To give you some background, I have a UIView subclass called ProgressView. Within the ProgressView, there is the Progress Bar UIView. The Progress Bar UIView is connected as a referencing outlet progressBar to the ProgressView class. Here's how the fairly simple hierarchy looks within the Storyboard:
As you might have guessed, I'm building a custom progress bar. The ProgressView UIView is the "track," and the Progress Bar UIView is the "fill." The plan is to use the trailing constraint of the Progress Bar UIView to make changes to the progress.
Existing configuration
The constraints for the Progress Bar UIView are set up to be flush with the superview (Progress View):
The trailing constraint of the Progress Bar UIView is connected to the ProgressView class as a strong outlet: #IBOutlet var trailingConstraint: NSLayoutConstraint!.
Within the ProgressView class, there is a setProgress function. This function is called from the ViewController which contains the ProgressView. Here's the function with comments explaining how it works:
public func setProgress(_ progress: Double, animationDuration: Double) {
// Pre-conditions
guard progress >= 0.0 && progress <= 1.0 && animationDuration >= 0.0 else {
return
}
// Calculate the new constraint constant based on the progress
let newConstant = CGFloat(1 - progress) * self.bounds.size.width
// If the update should be made without animation, just make the change and return
guard animationDuration > 0.0 else {
trailingConstraint.constant = newConstant
return
}
// Set the constraint first
self.trailingConstraint.constant = newConstant
// Animate!
UIView.animate(withDuration: animationDuration) {
self.layoutIfNeeded()
}
}
The problem
As you can see, this function can update the progress with and without animation. However, when called from the ViewController, neither works. The trailing constraint constant appears to remain 0, and the progress bar fills the entire track.
Attempted solutions
I have tried calling layoutIfNeeded() and setNeedsLayout() in every position and configuration where it makes sense (on both self [where self is the ProgressView] and on progressBar). I have tried putting the code explicitly on the main thread with DispatchQueue.main.async. As a ground truth test, I tried connecting the trailingConstraint outlet to the ViewController itself and updating the constant from the viewDidAppear() and viewDidLayoutSubviews() methods. Nothing works.
A possible hint in the right direction
Here's the weird part. The progress bar appears filled on the screen, but when I debug the view hierarchy, the progress bar looks correct. The trailingConstraint constant seems to have been set correctly.
I was testing on an iOS 10 device and running Xcode 8.3.3. This issue seems really odd to me, and I'm not quite sure what to do from here. Thanks for your help.
I faced the same problem.My problem was solved by changing constant in animation .Try this out:
UIView.animate(withDuration: 0.2) {
self.trailingConstraint.constant = newConstant
self.layoutIfNeeded()
}

Animating UINavigationController height during controller transition

I am designing an iOS app in swift, and I am having some difficulty with animations during a controller transition. Specifically, I've implemented a UINavigationControllerDelegate, to listen for when a certain view is pushed. When this view is pushed, I want to hide a bar at the bottom of the screen. My code is working almost perfectly, however whenever I begin an animation on the height of the navigation controller, the current view (which is being removed) animates its height correctly, but the new controller which is being pushed already has the new height from the animation. To put some code to it, the following function is called from my UINavigationControllerDelegate's willShow viewController function:
func animatePlayerVisibility(_ visible: Bool) {
if visible == showingPlayer {
return
}
showingPlayer = visible
let height: CGFloat = visible ? 56.0 : 0.0
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.35) {
self.playerHeight.constant = height
self.viewBottom.constant = height
self.view.layoutIfNeeded()
}
}
'playerHeight' is an IBOutlet to a constraint on the height of the player container view. 'viewBottom' is also an IBOutlet constraint between the bottom of the top container view and the bottom of the screen. Essentially, as long as these two constraints are animated together, it should look nice.
To help visualize the graphical bug, I edited this line
self.viewBottom.constant = height
to
self.viewBottom.constant = height * 2.0
I have created an imgur album of the actual wrong behavior in action:
http://imgur.com/a/znAim
As you can see, the old view controller animates properly, when the new controller already has the new animated size.
Here is the layout of my storyboard:
Any help would be really appreciated. I've been trying to fix this for a while with no success.
EDIT: The view of the animation without the *2 applied.
https://imgur.com/a/2a5Sw
Have you thought about not using UINavigationController? Maybe it will be easier to use ChildViewControllers mechanism. Then with it you can use a powerful autolayouts and have more control over animation (in your case height)
More info about this here
I've created a nice little sample project you can find here!
There are a number of things that could be going wrong, and since I haven't looked over your project personally it's likely I organized things very differently in my sample, but hopefully you will understand it. I think the big thing is that I added a constraint in storyboard to the navigationController's container to the bottom of the root viewController. I don't adjust the height of this container at all when I animate.

change autolayout constant when keyboard shows

I'm having a TopContainer View then a Scrollview and then again a BottomContainer view.
Now The constraints are that the scrollviews top is to the bottom of topContainer and that the bottom of the scrollview is to the top off the bottomcontainer.
When I start the viewcontroller and see the view hierarchy it is good.
But then when the keyboard shows I want to modify the autolayout constraint so that the bottomcontainer moves up.
So I thought I save the constraint and the change the constant like this:
private var toolbarBottomConstraint: NSLayoutConstraint?
self.toolbarBottomConstraint = self.toolbar.autoPinEdgeToSuperviewEdge(.Bottom, withInset: 0)
And then when the keyboard popups up I do this:
func keyboardWillShow(notification:NSNotification){
var userInfo = notification.userInfo!
var keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
keyboardFrame = self.view.convertRect(keyboardFrame, fromView: nil)
self.toolbarBottomConstraint?.constant = keyboardFrame.size.height
}
This doesn't do anything. The keyboard height is exactly the offset the bottom needs to be on top of the keyboard.
Why is the view not changing? I always change constants like that and it always works. Do I need to do something special because it's now with the keyboard.
If you're building for iOS 9 or above, I wrote a simple library that takes care of automatically adjusting views when a keyboard appears. It's pretty flexible and it should work seamlessly.
If you do still want to roll this on your own, it looks like you haven't added a listener for the notification anywhere. You'll need to do that before messages are sent to the keyboardWillShow(_:) method.
Have you tried calling setNeedsLayout() after the set of the constraint?:
self.toolbarBottomConstraint?.constant = keyboardFrame.size.height
self.view.setNeedsLayout()

UIView Not Changing Size On Rotation Autolayout

I have a UIScrollView with a UIView for content inside of it. On the UIView, I have some buttons I'm using for a menu. Everything works great until I rotate to landscape. Once I rotate, the buttons on the UIView can't be clicked but the top buttons still work. I'm assuming it's because the UIView holding the buttons isn't being resized so it's not responding to taps. This can be seen in the 3d exploded image (Image 4). I have tried all of these lines of code to try to get it to resize, buttons have worked (code is commented because I tried different combos).
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
//hides and displays tab bar on orientation changes
if toInterfaceOrientation.isLandscape {
tabBarController!.tabBar.hidden = true
//self.navigationController!.view.sizeToFit()
//self.navigationController!.view.setNeedsLayout()
//self.viewForContent.setNeedsLayout()
//self.viewForMenuItems.setNeedsLayout()
//self.viewForContent.contentSize = CGSizeMake(900, 900)
//self.viewForContent.setNeedsLayout()
}
else if toInterfaceOrientation.isPortrait {
tabBarController!.tabBar.hidden = false
//self.navigationController!.view.setNeedsLayout()
//self.viewForContent.setNeedsLayout()
//self.viewForMenuItems.setNeedsLayout()
//self.viewForContent.contentSize = CGSizeMake(900, 900)
//self.viewForContent.setNeedsLayout()
}
}
Also I have this in the DidLoad and DidLayoutSubviews. When I comment out the DidLayoutSubviews the buttons work again, but the scrollview isn't large enough to allow me to scroll to the bottom buttons when in landscape
override func viewDidLoad() {
super.viewDidLoad()
let size = UIScreen.mainScreen().bounds.size
viewForContent.contentSize = CGSizeMake(size.width, 458)
// Do any additional setup after loading the view.
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let size = UIScreen.mainScreen().bounds.size
viewForContent.contentSize = CGSizeMake(size.width, 458)
}
The My Information, My Staff, My Colleagues, and My Residents are the buttons that work in landscape mode.
I have used autolayout constraints to setup the views. Also, I am using XCode7 with Swift.
Any help would be appreciated
Thanks
First I setup my views as outlined in the link posted above in the comments by Kusul (Thank you). But when I did this and added the buttons to the content view they wouldn't show on when the screen was rotated to landscape because they were off of the bottom and the UIScrollView didn't "grow".
I figured out that I needed to add a bottom constraint to my last button. So the last button now has a constraint to the top and the bottom. This forced the scroll view to "grow" to the proper size. I could then eliminate setting the size in the didLoad and didLayoutSubviews. Thanks for the help

Resources