Resize animations on a scrollview - cocoa

I am trying to create a resize toggle animation on this simple custom TUIScrollView class (from TwUI open source project and very similar to UIScrollView) that I have built. It is called TUILayout and supports horizontal layout as well as vertical, animated insertions and removals and has a more declarative way of supplying data to it's cells that I prefer over delegation. It recycles views similar to TUITableView or UITableView. Anyway if you want to follow along, it's just one class and is here.
https://github.com/mrjjwright/TUILayout.
In the example code, the user clicks the blue button in the lower left and all the rows shrink smoothly to a size where the user can reorder and delete some rows (right click on a row in the example to see this in action), etc... and then the user toggle the rows back out to their original size by clicking the blue button again.
While doing this resize in setObjectHeight:animated I first resize my model objects that represent the rows, recalculate and set the contentSize on the TUIScrollView, cycle in all the new views (say 10 more views will fit in the shrunk view so dequeueReusableView and addSubview gets called 10 times) and finally I animate the frames of all the views to their size and location in layoutSubviews.
The result is that the scrollview correctly shrinks to a size where the scrollbar no longer displays, the views that are on screen animate smoothly down to their reduced size, but the newly added subviews that can now fit in the visibleRect animate in much later as one block of subviews.
So all the newly added subviews lag behind the views that were on the screen and I can't figure out why the animation isn't all happening together. I have tried lots of different combos of things with no luck including CATransactions. I am wondering if it has to with how a CAScrollLayer works or if somebody can help me think through this.
The more general issue is how to smoothly handle resizing animations on scrollviews that recycles their views and I have looked at several other grids out there in the iOS world and have got some inspiration but am looking for more.
Thanks!

I think I might have solved my own issue here (as I was making my bed this morning it hit me). I forced the current runloop to run after cycling in all the necessary subviews and very importantly not setting the contentSize of the scrollview until after the run loop completes and adds the needed subviews for the animation. In order to get the run loop to fire I used the trick from this SO question:
skipLayout = YES;
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
skipLayout = NO;
If skipLayout is set TUILayout just returns from layoutSubviews so that the views just added are not immediately removed by the recycling layout code. Forcing the run loop to run made sure that all subviews were on the screen for the animation. After this I performed the resize animated layout. I updated the code on github if anybody is interested. I will leave this question open for a while to gain some further insight.

Related

isOpaque not stopping passing to parents drawRect

I got a problem with Cocoa and its View redraw hierarchy.
I'm currently testing displaying (audio) levels in a meter style control and I'm using the MeteringView class from MatrixMixerTest example project from apple. This class is drawing the meter and only drawing the difference what got changed which looks like a very efficient class.
My project is splitted into 2 splitviews, in some are NSCollectionViews (Scrollview, Clipview) and in others are only static views. If I add the meter to those "static" views they work fine when these views call setNeedsDisplay:YES. If a meter is added to the view of a CollectionView Item it gets rendered, but loosing its drawn "old level" parts and its corners/background. I think this happens because the CollectionView item gets also called to be redrawn (which has a background image) and everything is gone. It is drawing some parts whats currently changing (the drawing works).
Is there a way to prevent the Item itself to be redrawn? Or, I dont know why it is not happening in those static views, because those views also have background images but do not draw over the meter.
Are there some tricks or whats different in a CollectionView than in a "normal" view?
EDIT: After reading about isOpaque (MeteringView isOpaque = YES) means it should not call the parent views drawRect if set to yes. Well that works for the static views, those MeteringViews do not call parents drawRect, but those in a CollectionView do however. I dont know why.
EDIT 2: I gave this topic another title, because isOpaque=YES in MeteringView is not stopping calling the parents drawRect in a CollectionView, in a normal view it is working. Are there some things to know about? I have to stop redrawing the CollectionView Item because thats the problem.
Thanks in advance guys
Benjamin
isOpaque is just hint to the system. It does not prevent other views from drawing their contents, it only means that it can sometimes skip making other views update their contents.
If your view is opaque, it should draw itself as opaque and completely fill its bounds.

How to place an NSButton over an NSTableView

1) No table behind button
2) Table loaded
3) After scrolling
If I place a button over an NSTableView I get artifacts being left behind after scrolling. Does anyone know how to fix this?
My current solution is just to split the section with the table into 2. The lower portion is a disabled button in the background.
Try the NSScrollView method
- (void)addFloatingSubview:(NSView *)view forAxis:(NSEventGestureAxis)axis
All tableviews should normally be inside a ScrollView.
I had a similar problem and solved it, see Why does an NSTextField leave artifacts over an NSTableView when the table scrolls?. Essentially OSX will draw the contents of the table and let the parent scroll view move the cached contents avoiding redraws to be performant. By subclassing the parent scroll view it can be forced to refresh the table hooking the reflectScrolledClipView: method. Then the whole table, including overlays, will be redrawn with each scroll.
That's because the scroll view is set to copy its contents when scrolling and only redraw the newly-uncovered part as a performance optimization. To turn that off, use
myTableView.enclosingScrollView.contentView.copiesOnScroll = NO;
though that will make scrolling use more CPU (you can also do this in the XIB, look for a 'copies on scroll' checkbox).
Probably a better approach would be to switch the scroll view and the button to be layer-backed:
myTableView.enclosingScrollView.wantsLayer = YES;
myButtonView.wantsLayer = YES;
(Again, you can set this in the 'Layers' inspector of the XIB file, where you can click a checkbox next to each view to give it a layer) Now, the scroll view will only copy stuff from its own layer (which no longer includes your button). Also, now all the compositing of the text view will be done using in the graphics card. This works fine with an opaque push button, however, if you put text on a transparent background in its own layer (e.g. if you have a transparent pushbutton with a title), you lose sub-pixel anti-aliasing.

UIScrollView unintended content displacement in landscape mode

I' ran into an absolutely weird kind of problem I don't have a clue yet how to fix it, having been working on this without a break (except for eating, sleeping, drinking coffee and smoking) for almost two days now.
What I have:
I've got an UIScrollView inside my view controller, containing a certain number of textfields and a UICollectionView. With a button, I open another view controller for adding new data to the collection view. Back to my first view controller after the new data items have been added, I call invalidateIntrinsicContentsize on my UIScrollView and on my UICollectionView to resize them both to fit their content (I use Autolayout and let both of them hug their content). On both views I've implemented these methods to nicely fit their content, and this part works perfectly fine.
What my problem is:
If I add data to the UICollectionView in the way just described, and if I'm in landscape mode, the problems start: After getting back to my initial view controller and updating the intrinsic content sizes, the content of my UICollectionView is displaced - displaced in such a way, that the scrolling position of the content inside the UIScrollView before switching to the data-adding view now becomes something like a fixed content offset. If I scroll up now, I can reach at maximum that position of the content where it has been - by getting scrolled to - before I switched to the data-adding view controller.
Pictures (everyone loves that):
To illustrate that situation somehow difficult to explain, I appended two pictures. Red is the entire content area of the scroll view, dark green is the content, where both overlap the color is olive. The visible part of the UIScrollView is shown in the highlighted, bordered area.
Before updating the data, content is where it should be:
(source: grubbrother.com)
After updating the data and updating UI, content is displaced by former scroll position / contentOffset:
(source: grubbrother.com)
Please help me with this weird stuff. Unfortunately, I've got a tight deadline and need to move on with the project. Nevertheless I really don't know how to solve this or even what might cause it - I have absolutely no clue.
Despite I couldn't find out yet what causes the problem described in my question, I found a hack around it that at least somehow fixes the symptoms:
As I describe above, the unwanted content displacement that occurs when switching back to the UIViewController containg the buggy UIScrollView is equal to the contentOffset of the UIscrollView before switching to another scene.
Knowing this, I set the contentOffset to zero after my UIViewController disappears.
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.scrollView setContentOffset: CGPointZero];
}
When the view reappears, I scroll down to the newly added content at the bottom of the UIScrollView in question:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.appearedFromActorsAdding)
{
[self.scrollView setContentOffset:CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.frame.size.height) animated:YES];
}
}
Hope this helps anyone running into the same problem.

UIScrollView zooming and GUI management

I would like to programmatically allow the user to zoom away from a current page inside of UIScrollView and present them with an overview of multiple pages. Then allow them to touch/choose one of the pages to zoom in.
I have multiple sub ViewControllers for each page. The important aspect is that each ViewController contains detailed information, so I want most of that information to be visible when they get a "birds eye view" of what's happening.
What's the best way to do this?
Additional detail: pretend each UIViewController has a UiTableView within them. There's 5,6,3,1,0,10 Cells in each of these (respectively) is there a way to show all Cells at once in the larger view?
Perhaps is there a way to screenshot the Views and present them as smaller objects?
Currently I have the UIPinchGestureRecognizer already working, just need a way to control the transition of these viewcontroller into the middle. Is there a way to screenshot each controller and transition to a different view for selection?
If You want it for a pdf viewer, then maybe you can implement something similar to this:
https://github.com/vfr/Reader
When corresponding button is pressed - modally rises up a gridview with smaller pages. When taped on any - it then opens that page.
If it's not about pdf viewer, and You still desire the pinch-zoom effect, then maybe you can implement two different views - where - on one will be in grid - all viewcontroller thumbnails, and on other - scrollview. When you pinch -zoom out - scrollview alpha changes to 0, while gridview list alpha changes to 1. When taped on any viewcontroller thumbnail - alpha changes.
If You still want without fade-in fade-out, then it's even harder. On each pinch zoom movement, you need to recalculate each viewcontroller positions and sizes. and start moving them where they should be.
Hope that helps.. somehow..

How to "stick" a UIScrollView subview to top/bottom when scrolling?

You see this in iPhone apps like Gilt. The user scrolls a view, and a subview apparently "sticks" to one edges as the rest of the scrollView slides underneath. That is, there is a text box (or whatever) in the scrollView, that as the scrollView hits the top of the view, then "sticks" there as the rest of the view continues to slide.
So, there are several issues. First, one can determine via "scrollViewDidScroll:" (during normal scrolling) when the view of interest is passing (or re-appearing). There is a fair amount of granularity here - the differences between delegate calls can be a hundred of points or more. That said, when you see the view approach the top of the scrollView, you turn on a second copy of the view statically displayed under the scrollView top. I have not coded this, but it seems like it will lack a real "stick" look - the view will first disappear then reappear.
Second, if one does a setContentOffset:animated, one does not get the delegate messages (Gilt does not do this). So, how do you get the callbacks in this case? Do you use KVO on "scroll.layer.presentationLayer.bounds" ?
Well, I found one way to do this. When the user scrolls by flicking and dragging, the UIScrollView gives its delegate a "scrollViewDidScroll:" message. You can look then to see if the scroller has moved the content to where you need to take some action.
When "sticking" the view, remove it from the scrollView, and then add it to the scrollView's superview (with an origin of 0,0). When unsticking, do the converse.
If you use the UIScrollView setContentOffset:animated:, it gets trickier. What I did was to subclass UIScrollView, use a flag to specify it was setContentOffset moving the offset, then start a fast running timer to monitor contentOffset.
I put the method that handles the math and sticking/unsticking the child view into this subclass. It looks pretty good.
Gilt uses a table view to accomplish this. Specifically, in the table view's delegate, these two methods:
– tableView:viewForHeaderInSection:
and – tableView:heightForHeaderInSection:

Resources