I have a set of UIViews and their controllers representing different pages in a "document." All are under the control of Apple's UIPageViewController.
The pages do not necessarily have static graphic content. The content can change based on user actions when viewing any given page. When that happens, my code invalidates the UIViews for all the appropriate pages using setNeedsDisplay.
But the view drawing update doesn't occur until a page is displayed >after< the UIPageViewController does all its magic animating a page turn.
The problem is that during that animation, UIPageViewController appears to use a cached version of the pixels of the old page. The user sees the old content up until the page-turning animation is finished, at which time, there's one last update (because of the earlier setNeedsDisplay call) and then the final correct version of the page is displayed. But the user sees this (flashing) change, which makes no visual sense, and violates the GUI illusion that what you see represents what you imagine.
I can't call each view's drawRect: method to force the pixels to change; drawRect: is reserved for the system to call.
So how does one get the UIPageViewController to update or toss a cached page to prevent this visually confusing situation?
Related
I have a feature that allows users to tap the side of the screen to go through content (like instagram stories). The information is presented in different formats:
example of screen types
Currently, I am just creating UI elements (through CGRect) and placing the content on the screen according to the template type that comes in. There are many screen types and the canvas has to repaint with each switch. Should I be utilizing tableView for speed (development or otherwise)?
Repainting on condition
Is this bad practice to constantly reload and recreate items with each tap?
A table view is a vertically scrolling sequence of "cells" and has nothing in common with the situation you are describing, as far as I can see.
Actually what you are describing might be a UIPageViewController, which lets you "page" through entire screens of material and yet create each "page" dynamically according to what comes "next" in the series.
Each page would be its own view controller and could configure its own views. There would be no subviews to remove; rather, the "next" view is just sitting there waiting to be asked for. Each time the user changes the "page", the old screenful of views just goes away after sliding off the screen.
In my app, I have a very custom UITableView. The cells are all statically defined in Interface Builder, but based on the data structure the table morphs in many various ways. For example, if some data doesn't exist, some cells (or entire sections) are not displayed, custom separator lines are added to account for missing cells, extra views are loaded into the cells, VoiceOver labels change, etc. Because all the cells are static, I set up the table layout in viewDidLoad because I always have the data available at that time. I have always presented this view controller modally, which has worked great. If the user wants to display different data in this table they have to dismiss the view controller and pick a different item to present it again, and it gets rendered appropriately in all cases.
But now I am converting this into a split view controller for iPad, so this UITableViewController never disappears off screen, but I need to set up the table again when the user taps an item. The problem is, because the table is never deallocated, its previous layout still exists when I load more data into it. It would be a lot of work (and an excellent opportunity for many difficult to reproduce bugs to pop up) to test all possible scenarios and try to reset it back to its "pre viewDidLoad state" or undo those previous layout changes if not relevant anymore, if not impossible because I don't have references to the many different custom separator lines generated.
My question is, is it possible to completely reset the table view controller every time a row is selected in the master view controller, therefore allowing it to properly set up the layout because it is not stuck with the previous layout?
I essentially need some way to completely wipe it clean as if it never did any setup, then instantiate it again to cause viewDidLoad get called (or I can move that code to its own method or viewWillAppear). I'm basically looking for a way to reset the tableView back to how it is defined in Interface Builder.
I believe this would result in a flash because the table would completely disappear then reappear in a different format, but that would be acceptable. If that can be animated that'd be nice. If this is really not recommended at all, how do you suggest I proceed to ensure the layout is always appropriate for the data it is presenting?
I was over-thinking this. There's really no need to completely throw away the table and generate a new one. It turned out to be simpler than I had thought to reset the table back to its default state. Just had to be sure to catch every possible thing that could change, including VoiceOver labels, and reset to nil or the default value. Then it can run through the reset code then the layout code every time the data changes and render an appropriate layout. The most difficult part was to remove the custom separator lines, which I solved by adding each one to an array when it's created, then index through it and remove each one from its superview then remove the Autolayout constraints associated with it. One can wrap all of this into a UIView animation block to get a nice fading effect. It's working quite well.
I have to implement a view controller (on iPhone, portrait only, full screen view) where the upper part of the view must have an horinzontal, paged scrolling behavior, potentially infinite.
I already used for similar purposes UIPageViewControllers, to take advantage of the datasource and delegate protocols, which are very helpul for manage memory and other stuff (keeping only 3 view controllers in memory, providing delegates to handle actions exactly when a transition is done and so on): so I think that in this case too this component is the best choice.
But here comes my problem. In the view I'm realizing, I have to let the user understand that he can swipe left and right to move to another view: a page control is not a good choice, since the scroll could be potentially infinite, so I would like to let a small portion of the views of the left and right view controllers to be visible.
Something like that:
link to the image (sorry I cannot include images in my posts yet)
Up to now I have not been able to figure out how to realize this. In the options during initialization, UIPageViewControllerOptionSpineLocationKey can be specified to set (from documentation) "Space between pages, in points": but this seems to work only with positive value, so that the space increases, while it ignores negative values that could reduce the space.
I hope there might be a solution using page view controllers, since at the same time I need to refresh a table view in the lower part of the screen when a transition is complete, and the delegate method of page controllers is ideal for this aim.
Maybe a collection view can be an alternate solution, but it is more complicated and I'm not sure how to obtain a behavior like the one I described to refresh the table view.
If needed I can attach some code and a screenshot of the prototype
Ok, I understand that this is not possible and why it is.
The datasource methods load, when needed, the view controllers that are before and after the current one. Making these view controllers' views always visible, as I desired, will require that the datasource loads more than one view controllers after (or before, depends on the direction of scrolling) the current one, to be ready for the pan actions (potentially, before the animation is ended by the user lifting up its finger, two view controllers "after" or "before" could become visible it my desired configuration), and this is not intended by UIPageViewController on iPhone, especially in portrait mode.
So actually, the only way to achieve that more than one view is visible in an horizontal-scrolling component at any time, is to implement a UIScrollview with horizontal paging and calculate the contentSize and other sizes accordingly.
My UIViewController has a UIPageViewController embedded in it. The pager can contain anywhere from 5-38 pages (each page is an instance of a UIViewController subclass) depending on the situation. I've noticed that depending how complicated I make the UI elements on each page, the app slows down considerably, and is very slow when swiping to go to the next page.
Here's the thing - the view on each page is identical, except for the values of a few UITextViews. I am building the view in each page's viewDidLoad method each time viewControllerAtIndex is called for a new page. I feel like there must be a way to re-use the same view for each page, and just swap the text values that are supposed to be different. Can anyone describe a strategy to do this?
Like I said, every page has an identical view hierarchy except the values for some of the text, so I'm really just looking for a way to maintain one basic view controller per page, but cache the view hierarchy to be re-used on each page, and swap out some simple text values depending on page number.
EDIT
Something I forgot to mention in the original post is that I'm building my view programmatically because the number of elements on the page is dependent on choices the user made on previous screens. i.e. there may be 5 TextViews or 10 TextViews, etc. depending on what the user selected on a previous screen (before coming to the UIPageViewController). With that said, I do not believe an xib based approach will work because the initial layout is dynamic.
Thanks in advance!
Have you tried using a xib based view, which you could pull out of the xib once, and keep a strong reference to it. In your controller's viewDidLoad, you could set its view to this xib base view, then populate the text view(s) with the proper text.
Greetings! I'm trying to borrow the view flip concept from Apple's TheElements sample app. This sample employs a container UIView in which you can swap between two subviews. The flip is achieved using setAnimationTransition:forView:cache: and removing/adding each subview.
In general, the flip works and I can swap between my two views (a UITableView with headers/footers, and a MKMapView). However, various (consistently chosen) areas of my table view are obliterated (using the table view's background color) before and after the table view is flipped, and I don't understand why.
I can't find anything unusual about the table view in terms of drawing. In the case of the sample app, the view is drawn from scratch, but I would hope that doesn't factor in to it! I'm really hoping it's something simple - maybe a UITableView property setting?
Clues appreciated. Thanks!
Update: When I slow down the animation, I begin to get an understanding of what is being disturbed (though I still don't know why). My table header view contains an image view and label view. Now, imagine the CGRects for those two views (only without any visible content, just a background color) being redrawn further down the table view, right over the table rows.
This also happens with another chunk of real estate that appears to come from a table view cell that was set with a custom height (to size the text within it).
I dropped a gratuitous number of breakpoints throughout my code (where things are sized, created and whatnot), and not one of them is hit during the transition.
I even tried placing my table view inside a UIView and targeting that for the transition instead. No difference.
From the API docs:
Caching can improve performance but if
you set this parameter to YES, you
must not update the view or its
subviews during the transition.
Updating the view and its subviews may
interfere with the caching behaviors
and cause the view contents to be
rendered incorrectly (or in the wrong
location) during the animation. You
must wait until the transition ends to
update the view.
That's all well and good, but I'm not updating any views or subviews during the transition. (At least not on purpose! Again, see the note about the breakpoints. Nothing got hit.)
Amazing.
Answer: In this case, it appears to only happen in ... the Simulator! On the device, it's fine.
I was thinking that such a run-of-the-mill transition would be the sort of thing that would render identically on the Simulator and the device.
I was wrong. So if you ever see animation transition glitches on the simulator, hang in there. It might be fine after all.