I have two scroll views, one of them with constraints that make it take up the full parent view, and the other right next to it, but hidden outside the bounds of the parent view. I want to animate both sliding left until the second scroll view takes up the full parent view, and the first scroll view is now out of bounds on the left. How do I do this for an OS X app using Swift?
Figured this one out with a little hunting and piecing things together.
Create an IBOutlet for the constraints that you want to change in the animation. In this case, use the leading constraint for each scroll view.
#IBOutlet weak var ScrollView1LeadingConstraint: NSLayoutConstraint!
#IBOutlet weak var ScrollView2LeadingConstraint: NSLayoutConstraint!
Then, use the following:
NSAnimationContext.runAnimationGroup({ (context) -> Void in
context.duration = //length of the animation time in seconds
self. ScrollView1LeadingConstraint.animator().constant = //negative width of scroll view
self.ScrollView2LeadingConstraint.animator().constant = 0
}, completionHandler: { () -> Void in
//insert any completion code here
})
This will animate the first scroll view out of frame on the left, and move the second scroll view into its former place.
Related
On a Mac, Mail and Finder have a solid looking scroll on their table views when the up or down arrow is held. The row highlight sits flush with the top or bottom of the column and the rows step through with no animation.
8 years ago it seems that it was hard to not do this. Now I can't seem to stop scrollRowToVisible on an NSOutlineView animating.
I have tried wrapping the call with NSAnimationContext.beginGrouping() or CATransaction.begin() etc to set any animation duration to 0.0 but no luck.
Is there anyway to make this call snap - or should I be using something a little lower level?
EDIT
Here is my code. The duration has no effect here. There are always a few frames of scroll animation, and the endpoint of the animation is slightly irregular (i.e. the bottom edge of the scrolled to view is not always aligned with the bottom edge).
if selectedRows != outlineView.selectedRowIndexes {
outlineView.selectRowIndexes(selectedRows, byExtendingSelection: false)
// I would love this not to animate like in mail, but it cannot be stopped!!!
if selectedRows.one {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.allowsImplicitAnimation = false
NSAnimationContext.current.duration = 0
outlineView.scrollRowToVisible(selectedRows.first!)
NSAnimationContext.endGrouping()
}
}
Using runAnimationGroup has the same result:
NSAnimationContext.runAnimationGroup( { current in
current.allowsImplicitAnimation = false
current.duration = 0
outlineView.scrollRowToVisible(selectedRows.first!)
}, completionHandler: nil)
I have variable height rows in my table but I don't see why this would make a difference. From the above code, the change in selection is always highlighted before any movement in the table, further indication that the scroll animation is not being removed.
I had this problem myself, and solved it by subclassing NSClipView and overriding func scroll(to newOrigin: NSPoint) like this:
override func scroll(to newOrigin: NSPoint) {
super.setBoundsOrigin(newOrigin)
}
This should disable smooth scroll entirely, which is the animation effect you are describing, for the scroll view that houses your subclassed clip view.
I use Autolayout for a fairly complex Menu and really need it. All Buttons, UIViews etc. of my Menu are in a separate UIView called "menuSubview".
If the user presses a button the whole "menuSubview" shifts to another position to reveal other parts of the menu. Sometimes the buttons in the menuSubview move as well. I always save the "Menu State" (with Userdefaults in the get-set variable "lastMenu") and have a function to set the alphas and centers according to the saved "Menu State".
I tried calling the "openLastMenu" function in viewDidAppear, viewDidLayoutSubview - all the "viewDid" functions of the ViewController. The "menuSubview" center and alphas of the buttons always behave as expected... but the centers of the buttons simply won't - no matter what "viewDid" I call the function in.
(the code is a lot more complex - I boiled it down to debug and state my point)
override func viewDidAppear(animated: Bool) {
if lastMenu != nil {openLastMenu()}
}
func openLastMenu(){
menuSubview.center.x = view.center.x //works
menuSubview.center.y = view.center.y + 200 //works
button1.center.x = view.center.x - 50 //why you no behave???
button2.center.x = view.center.x + 50 //why you no behave???
button3.alpha = 0 //works
button4.alpha = 0 //works
}
For debugging I even made a button Subclass to fetch the "center" values with a "didSet" if they change. Seems like after taking the correct values they change once more to their Autolayout-Position.
...oh and ignoring the constraints with "translatesAutoresizingMaskIntoConstraints" on the buttons always fucks up the whole menu. I'm starting to get crazy here :)
If you position views using autolayout, any changes to the frame, like what you do here with the center property, will be ignored.
What you need to do is identify the constraints that are you need to change to move the views in the desired position. Example:
You want to move button1 50 points to the left of view.center. Assuming view is the superview of menuSubview, you would
1) deactivate the the constraint responsible for button1's horizontal placement. How you do this mainly depends on whether you created the constraints in code or Interface Builder. The latter will require you to create outlets for some of the constraints.
2) create a new constraint between button1's centerX anchor and view's centerX anchor with a constant of -50, like so (iOS 9 code)
button1.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor, constant: -50.0).active = true
I have 4 containers and need to move them up 40 pixels together. Can i move all of them at once.
I have tried changing the rect of them one by one, but when rotation occurs everything is messed up.
It is very easy actually. In IB create your top, bottom, left and right constraints with heights as well. And then ctrl drag the top constraint with connects the top view to the top of the superview.
Go to your class and just give the desired constant value as you wish.
#IBOutlet weak var moveConstant: NSLayoutConstraint!
and then
#IBAction func moveTop(sender: AnyObject) {
moveConstant.constant = 40
}
I have 3 Views in IB. View One(1) has a view and an UIImage that I am using as the background for this ViewController. View Two(2)(Details View) and View Three(3)(Options View) are Container Views, both placed on top of each other. I have a button in View 2 that animations View 2 off to the left and moves View 3 on from the right. And I am able to go back from View 3 moving off to the right and bringing View 2 back on from the left.
Here is what my Document Outline looks like:
The White BG, is just a background under the title bar, just in case anyone is wondering.
Here is the code that I am using to do those animations:
func showOptions(){
println("show options")
optionsView.center.x = self.view.bounds.width + self.view.bounds.width / 2
optionsView.hidden = false
UIView.animateWithDuration(0.5, animations: {
self.blurView.alpha = 1
// slide containers around
// println(self.view.bounds.width)
self.detailsView.center.x = -self.view.bounds.width * 2
self.optionsView.center.x = self.view.bounds.width / 2
})
}
func showDetails(){
println("show detials")
optionsView.center.x = self.view.bounds.width / 2
UIView.animateWithDuration(0.5, animations: {
self.blurView.alpha = 0
// slide containers around
// println(self.view.bounds.width)
self.detailsView.center.x = self.view.bounds.width / 2
println("\(self.optionsView.center.x) this is center X value 1")
self.optionsView.center.x = self.view.bounds.width * 2
println("\(self.optionsView.center.x) this is center X value 2")
}, completion: {
(value: Bool) in
println("\(self.optionsView.center.x) this is center X value 3")
})
}
Those that are curious as to what I get back from the print statements
When I don't try to change out the background photo and things work like this should I get this:
160.0 this is center X value 1
640.0 this is center X value 2
640.0 this is center X value 3
When I change out the photo I get these values:
160.0 this is center X value 1
640.0 this is center X value 2
160.0 this is center X value 3
What doesn't work is when I try to change out the image of the background view for this view controller.
let par3 = UIImage(named: "par3.png")
backgroundImage.image = par3
// println("par three")
showDetails()
When I change out the background photo, both View 2 and View 3 animate onto the screen from the left. I'm just not sure why. Does it have something to do with the width of the View Controller being modified when I switch out the background photo?
Thanks for the help!
It feels like you are only half embracing Auto Layout. You're using it to lay out your views initially, but then you're not using it when you're animating the views. You're modifying the center x position, which is the old way of animating. It's kind of working, but then I think everything is getting reset to your initial constraints when you swap out the background image, since that causes a call to layoutIfNeeded() under the hood, which uses the constraints to position everything back how it was initially.
Instead of modifying the center x position of the views you need to change your approach to modify the constraints instead. This will mean that even when you swap out the image your constraints will still be accurate and everything should work as expected. This requires setting up your constraints in a particular way, which I'll try and demonstrate.
First I have set up a parent view controller with a couple of child view controllers. Each of the children have a button that calls a delegate method back in the parent.
My storyboard looks like this:
Here's what the inspector looks like:
A few points about the storyboard setup:
I have a main container view that is a subview of the parent view. This main container view has clipSubviews set to YES, so that when we move views to the left and right they don't show up outside the bounds of the main container.
Both the options and detail container views have constraints set to match the width and height of the main container view.
The left side of the detail container view is set to butt up right against the right side of the options container view, i.e. [options][detail]. They are not directly on top of each other.
The detail container view has a constraint so that it's top edge lines up with the top edge of the options container view.
The options container view is set up with constraints to line up with the top left of the main container view. This combined with the previous 2 bullets means that initially the detail container view is off the screen to the right. I have a reference in the parent view controller to the constraint that lines up the left side of the options container view with the left side of the mainContainerView (the one that is selected in the above screenshot), and that's what I use to animate everything.
This is what the parent class looks like:
class ParentViewController: UIViewController, OptionsViewControllerDelegate, DetailViewControllerDelegate {
struct Constants {
static let optionsEmbedSegue = "optionsEmbedSegue"
static let detailEmbedSegue = "detailEmbedSegue"
}
#IBOutlet weak var mainContainerView: UIView!
#IBOutlet weak var optionsLeadingSpaceConstraint: NSLayoutConstraint!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == Constants.optionsEmbedSegue {
let childViewController = segue.destinationViewController as OptionsViewController
childViewController.delegate = self
} else if segue.identifier == Constants.detailEmbedSegue {
let childViewController = segue.destinationViewController as DetailViewController
childViewController.delegate = self
}
}
func optionsViewControllerDidSelectToShowDetail(optionsViewController: OptionsViewController) {
animateOptionsToNewXPosition(-CGRectGetWidth(mainContainerView.bounds))
}
func detailViewControllerDidSelectToGoBack(detailViewController: DetailViewController) {
animateOptionsToNewXPosition(0)
}
func animateOptionsToNewXPosition(xPosition: CGFloat) {
optionsLeadingSpaceConstraint.constant = xPosition
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.mainContainerView.layoutIfNeeded()
})
}
}
If you take a look at the optionsViewControllerDidSelectToShowDetail method, you'll see the line animateOptionsToNewXPosition(-CGRectGetWidth(mainContainerView.bounds)). This is moving the options view controller off to the left in the amount of the width of the main container view. This means that it will disappear off to the left of the screen, and because of all the other constraints, it will drag the detail view controller with it, revealing the detail view controller in the mainContainerView. The opposite happens in the detailViewControllerDidSelectToGoBack method, simply setting the constant in that constraint back to 0, which brings the options view controller back, and pushes the detailViewController off to the right.
I hope that helps.
i have a view inside a viewController, i wanted to start the smaller view outside the viewController in the left, and animate it to the centre when i press a button. so i made it like this:
override func viewDidLayoutSubviews() {
smallView.center = CGPointMake(smallView.center.x - 400, smallView.center.y)
}
And it works perfectly!, the problem is i have a text view inside that smaller view, and every time i start editing it it jumps outside of the main viewController right where it was, and i have to press the button again to bring it inside.
How to fix this?
PS: i tried positioning it to the centre when i start editing the text view like this:
func textViewDidBeginEditing(textView: UITextView) {
smallView.center = CGPointMake(smallView.center.x + 400, smallView.center.y)
}
But it doesn't work. and the method is connected to the textView properly(delegate)
PS2: i also have imagePickerController inside my viewController.
OK, as you're using Auto Layout.
The first rule of Auto Layout (you will see this in any Auto Layout book) is that you absolutely cannot touch the frame or center of a view directly.
// don't do these
myView.frame = CGRectMake(0, 0, 100, 100);
// ever
myView.center = CGPointMake(50, 50);
You can get the frame and center but you can never set the frame or center.
In order to move stuff around using Auto Layout you need to update the constraints.
For instance if I set up a view called myView and want it to grow and shrink in height I would do something like...
Set the top constraint to the superview at 0.
Set the left constraint to the superview at 0.
Set the right constraint to the superview at 0.
Set the height constraint to 50 (for example) and save it in a property called heightConstraint.
Now to animate the change in height I do this...
self.heightConstraint.constant = 100;
[UIView animateWithDuration:1.0
animations:^ {
[self.view layoutIfNeeded];
}];
This will then animate the height from 50 (where it was when I set it) to 100.
This is the same for ANY animation of views using Auto Layout. If (for instance) I had saved the left constraint I could change that and it would increase and decrease the gap from the left edge of the superview to myView.
There are several really good tutorials about AutoLayout on the Ray Wenderlich site. I'd suggest you take a look at them.
Whatever you do, I'd strongly suggest not just disabling Auto Layout. If you don't know how to use Auto Layout then you will very quickly fall behind with iOS 8 and the new device sizes.