I'm making an app in Xcode for Mojave OSX.
I want to make a resize of a collectionview and items position of that.
Currently I call to invalidateLayout() method, but it recalculate and set sizes and positions without animations.
In the GIF you could see what is the current behavior, I need that the last item go to second row, in an animated way.
For this i try to override this methods of NSCollectionViewFlowLayout
open override func prepare(forAnimatedBoundsChange oldBounds: NSRect){
}
open override func finalizeAnimatedBoundsChange() {
}
But these never get called, and I don't know what code I need to animate this transition.
(Moved from question to an answer.)
You could override the viewDidLayout of your viewController (or assign an observer to the frame view) and execute this:
collectionView.animator().performBatchUpdates(nil)
This reloads your collectionviewLayout with an animation.
Related
I am making weather app. I can swap between different locations using collectionview with enabled paging. Inside every page there is view with another collectionview where it shows weather for different time. When i drag and release on child scrollview, so it moves like it has kinetic energy, on the edge when it stops scrolling, master scrollview starts to scroll like it takes leftover energy from child scrollview. I dont want it to be sort of connected, so it needs to act more like in Apple's weather app.
Video of problem
Also would appreciate any tips on my design
EDIT:
Actually, there is collectionview inside scrollview, not two collectionview's
One way I can think of is to detect what is being interacted with and then prevent scrolling capabilities based on that.
One such way to do that would be to override hitTest for the main collection view.
Here is what you can try:
1. Create a custom UICollectionView subclass
class CustomCollectionView: UICollectionView
{
override func hitTest(_ point: CGPoint,
with event: UIEvent?) -> UIView? {
// Get the view you are interacting with
let view = super.hitTest(point, with: event)
// Allow interaction if we are interacting with the main
// collection view itself
if view is CustomCollectionView {
return view
}
// Return nil to prevent interaction since we are
// interacting with something besides the main collection view
return nil
}
}
2. Then create your main / parent UICollectionView only using this custom class
private var containerCollectionView: CustomCollectionView!
So hopefully this will
Restrict the scrolling interaction when you interact with the parent collectionview directly, not through its subviews
Preserve the scroll within the child collection
Give it a go and see if this helps
I want that the Bar Button Item diappear when I scroll down. My problem is that I don't know how to detect the scroll? I have tried some code but nothing worked. For example:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("123")
}
(Don't work, I call the method in viewDidLoad())
Also I don't know where I have to call the method. In viewDidLoad(), viewDidAppear() or somewhere else? I am new in Swift, sorry.
Does anyone know the answer?
The thing is you are calling it from viewDidLoad, that gets called only once when the view is loaded. You need to place this scrollViewDidScroll
function separately, after viewDidLoad for example, but not inside of it.
Also make sure you implemented UIScrollViewDelegate in your file. Then you can add scrollViewDidScroll method to detect when is the view scrolled and print('123') inside of it.
Also make sure to set your scrollView.delegate = self in your viewDidLoad.
I have built an app that uses no interface builder using Snapkit to create my Auto-Layout constraints. Everything looks fine in portrait, however a few screen's need some landscape specific constraints.
I've searched here and Google in general for a quick intro on doing this, but couldn't really find anything that was applicable (everything I found was based on using IB or used size classes instead of orientation - I specifically want landscape, not compact vs regular).
So, all my auto-layout constraints are set up in viewDidLoad at the moment. No doubt at very least, the ones that will be orientation dependant need moving to some kind of delegate/callback method on UIViewController, but I don't know know what that is...
How do I detect an orientation change in order to change my constraints?
How do I get the current orientation (so when I first load the view controller I can set the right constraints... Or is the function from my above question always called at least once for each VC on load?)
Outside of a ViewController, such as custom UIView's, how do I detect the orientation change? Should i send out a custom notification event? I'd rather not have my UIViewController tell every subview it has that orientation has changed.
Thanks for any help :)
I would personally use this function:
override func viewWillTransitionToSize(size: CGSize,
withTransitionCoordinator coordinator:
UIViewControllerTransitionCoordinator) {}
And then when those changes are detected call setNeedsLayout and layoutIfNeeded which should trigger a redraw of all of your subviews which can then handle setting constraints for specific orientations
I'm trying to automatically scroll my textview at the top before the view loads, so that you can't see it actually moving upward...
i'm trying doing so with
var zeroOffset = CGPoint.zeroPoint
textSpace.setContentOffset(zeroOffset, animated: false)
where textSpace is my UITextView.
when i place this code inside viewDidAppear it works, but the problem is that obviously you can see for a second the text scrolling, and it does't work at all when i place it inside viewWillAppear (viewDidLoad neither if can be of help).
I guess has something to do with the CGPoint that needs the view to be there before calculate the actual point but i'm not sure, is there a solution to this? thanks
In the viewController lifecycle, between viewWillAppear and viewDidAppear, there is a call viewDidLayoutSubviews. It is called after the subviews have been laid out. At that point, you will have everything you need for your call to work, but it will still be before the view appears. So override viewWillLayoutSubviews and place your call there.
I've included a link to a video that shows what problem I'm having:
https://dl.dropboxusercontent.com/u/39330138/Bug_Demo1.mov
There are two View Controllers, the first is non blurred and less important. When the plus button is clicked, the app segues to a new controller (without animating) and in prepareForSegue() I use UIGraphicsBeginImageContext and UIGraphicsGetImageFromCurrentImageContext to capture a UIImage from the current view and pass it on to the next one.
When the new view appears I use UIVisualEffectView to create a blur view and add it as a subview to the Image View that is the 'background'. Then, its opacity is animated at the same time the 2 views and 2 buttons are animated on screen with UIView animation and springWithDamping, giving the illusion of the view blurring over and items animating over the top.
The top view has a UITextField embedded in it which, when tapped calls becomeFirstResponder() and makes all overlaid (New Session, Tag & Button) views including the Visual Effect View imbedded in the background Image View disappear.
The reason I go into so much detail is because I'm not sure what exactly the problem is. However, I have a suspicion that it is to do with the AutoLayout/Size Classes in Xcode 6.
Does anyone know why this might be happening and how to fix it?
If you need additional information, just let me know.
Thanks!
EDIT:
When I log the views after I click on the TextField, all the frames seem the same.
EDIT 2:
Here's a link to a demo project will all the functionality from the video:
https://dl.dropboxusercontent.com/u/39330138/DEMO%20APP.zip
There are a couple of things happening here, but the main culprit is your use of viewDidLayoutSubviews(). This is called any time the system has to reevaluate the layout. I see you're setting your UIVisualEffectView's alpha to 0 in that method:
if !returningFromTagView {
blurView.alpha = 0
}
I think you're intending this to be called just once before the view appears because I see you animate the alpha to 1 in viewDidAppear(animated: Bool). However, any time the system reevaluates layout for any reason, viewDidLayoutSubviews() is called and the alpha on blurView is going back to 0 if returningFromTagView is false. That's why when you summon the keyboard (triggering a layout reevaluation), this view disappears. Xcode also warns you about making the alpha 0 in the console (it breaks the visual effect until the opacity returns to 1). Put the code above in the viewDidLoad() method instead, and you'll see blurView come back. The alpha only needs to be set to 0 once when the view loads.
The issue with the other views is a bit tougher to see, but the culprit again is your use of viewDidLayoutSubviews(). I imagine that you're puzzled why the views don't appear even after you've been very thorough in your keyboardNotfication() method to set the frames, bring the views to the front, make sure they aren't hidden, and then log this all. But after the keyboardNotification() method finishes, the layout system once again is triggered, and I see that you're nudging the views' frames here and there:
if returningFromTagView {
setX(-titleView.frame.size.width, v: titleView)
setX(-tagView.frame.size.width, v: tagView)
setX(-(cancelButton.frame.size.width + 20 + nextButton.frame.size.width), v: cancelButton)
setX(-nextButton.frame.size.width, v: nextButton)
} else {
setX(-titleView.frame.size.width, v: titleView)
setX(view.frame.size.width, v: tagView)
setX(-cancelButton.frame.size.width, v: cancelButton)
setX(view.frame.size.width, v: nextButton)
}
You're moving the views offscreen every time a layout change is made! Pause the program after you summon the keyboard and look at your view hierarchy using Xcode 6's great new Capture View Hierarchy ability. It's in Debug > View Debugging > Capture View Hierarchy. Those views are just hiding off to the side.
I image you're trying to do this just once when the view appears in order to support your transition animations, but it gets called whether the view is just appearing or if a small change like the keyboard is appearing. I suggest that you implement these animations another way, like using the views' transforms or using autolayout constraints (though you have a lot of missing constraints in the storyboard) to do your animation. viewDidLayoutSubviews() is really a place to fudge things here and there in your layout after the layout system has done its work. You should have a good reason for using it. It has the nice feature of overriding your autolayout constraints and letting you animate those views without toying with the constraints (because the method happens after the updateConstraints() and layoutSubviews() methods), and that's why we can't put the above code in a method like viewWillAppear(animated: Bool) instead (because autolayout constraints would counter the animation during layout later), but viewDidLayoutSubviews() just is not a method that's meant to support basic animations.
In spite of that, here's something simple to get your app going again and for you to see what's going on:
Make a property var comingFromSessionView: Bool property for your NewSessionVC view controller. In the prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) of SessionVC, add nextVC.comingFromSessionView = true
Then change the code block from viewDidLayoutSubviews() above to this:
if returningFromTagView {
setX(-titleView.frame.size.width, v: titleView)
setX(-tagView.frame.size.width, v: tagView)
setX(-(cancelButton.frame.size.width + 20 + nextButton.frame.size.width), v: cancelButton)
setX(-nextButton.frame.size.width, v: nextButton)
} else if comingFromSessionView {
setX(-titleView.frame.size.width, v: titleView)
setX(view.frame.size.width, v: tagView)
setX(-cancelButton.frame.size.width, v: cancelButton)
setX(view.frame.size.width, v: nextButton)
}
We'll switch these Bools to false during viewDidAppear after it's done with them:
override func viewDidAppear(animated: Bool) {
...
if returningFromTagView {
...
returningFromTagView = false
} else if comingFromSessionView {
...
comingFromSessionView = false
}
}
Now when the keyboard is summoned, your views are right where you left them!
The code above isn't great. I'd rather stay away from viewDidLayoutSubviews() for doing these animations. But hopefully you can see what's going on now. Your viewsDidLayoutSubviews() has been whisking away your views.