How do I efficiently manage screen size changes and redrawing with storyboards, initWithCoder, viewDidLoad and viewDidLayoutSubviews? - objective-c

I am using storyboard layouts to set up view layout.
I am supporting both iPhone and iPad layouts.
When the view is created with initWithCoder:, it is initially created with the frame size of the device I was last looking at in Interface Builder.
If I am designing with the iPhone X layout in interface builder and then build and run on an iPad, the view is initially created with iPhone X screen dimensions. Then viewDidLayoutSubviews: is called and it updates the screen dimensions to the correct iPad size.
The subviews are using drawRect inside UIViews to draw the view graphics. I am doing this so I can change graphic colors via code. I change the color variable and then call setNeedsDispay on the view to redraw the view with new colors using CGGraphicsContext commands.
It also allows me to draw any graphic image at any size. And with lots of graphics that means I don't have to include all the different images at 1x, 2x and 3x sizes in my bundle. It's all drawn dynamically.
Some of these images are laid out when the view loads and not in Interface Builder. So I check the screen size and draw the button size and position accordingly.
What happens is, viewDidLoad is called and it draws the graphics based on the initial screen size.
Then viewDidLayoutSubviews is called and I have to update the drawing of the subviews I placed manually repositioning them based on new screen dimensions and then calling the drawRect on them. I feel like this is just unnecessary extra work for the device.
In addition to that, viewDidLayoutSubviews is called for other reasons then just resizing the view on initial load of the viewController. So then each time it's called it will go redraw the subviews, even if they don't need it.
And, if the device I am running on is the same as the device I was using in Interface Builder, it doesn't call the viewDidLayoutSubviews. I can't just let the view layout the subviews there because there is no guarantee it will be called.
My solution so far is creating a variable to track the screen width. I set the variable in viewDidLoad. If it creates the view at iPhone X size, my screenWidthTracker = 1125. When viewDidLayoutSubviews is called I compare the current screen size to screenWidthTracker.
if (self.view.frame != screenWidthTracker) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateView" object:self];
};
if the view has changed size, it sends a message to redraw views. Any views I have placed manually as subviews are registered to listen for #"updateView".
Is there a better way to manage this? Is there a method that gets called ONLY when screen dimensions change and not when its updating the position of other subviews? Should I be utilizing viewDidAppear? I feel as though that is too late in the chain and I don't want the user to see button size updates.

In general, from the description you've given, I'd say that at the very most, you should be doing as much size work as possibly by using autolayout (which you can configure entirely within the storyboard) and then implementing only drawRect and viewDidLayoutSubviews — and the latter only if you need to.
A very common strategy is to use a boolean flag to set up initial conditions in the first call to viewDidLayoutSubviews, and not use it thereafter. Screen size changes after launch (not at launch), such as rotation, are detected by implementing willTransition(to:with:) — and even then you should check to see that the old size and new size are not the same (180 degree rotation) and do nothing if they are.

Related

How to change from NSView subviews to CaLayers in a MacOs App?

I have a music macOS Application that displays a piano roll with note events in a midi file. I am representing them as bars in a view, using a main NSView and a lot (typically 5000/10000) of subviews.This performs quite well, the only problem is on intensive tasks, like when I zoom the view, or I change the proportions from the GUI, I believe all the views receive a setNeedsDisplay message and resize accordingly. I was thinking,
Could I switch my implementation to CALayers instead of subviews ? the big NSView works in a NSScrollview. Perhaps I could try to implement some sort of lazy updating where only the visible notes get updated.
Thanks for any help.

Mechanisms to move a view partly offscreen

I have a problem that nobody has yet offered any help with so let me ask a technical question to help me diagnose it on my own.
What mechanisms does the system have for moving a view so it appears to be partly off screen besides changing the view's frame (i.e. bounds/center) or transform?
This is an app which needs to run on iOS 5/6 still so I'm not using auto layout.
I have a fullscreen view reached via a push segue.
After a pause I hide the (translucent) status and navigation bars.
When the bars hide with animation they slide up off the screen.
In iOS 5/6 this just exposes the top of the view (which is an image). In iOS 7 it moves the entire view up "offscreen" a corresponding amount (i.e. 64 points if I hide both bars) showing a bar of content "below" the view at the bottom of the screen.
When I un-hide the bars (via a tap) the bars appear and the view moves back down to occupy the whole screen.
Whether the VC is set to extend the view under bars or not doesn't affect the behavior.
I have not been able to reproduce this behavior in a simplified app.
In either state, i.e. partly offscreen or fully onscreen, the top-level view frame remains {0, 0} 320 x 480 and the transform matrix remains {1, 0, 0, 1, 0, 0}. I have no idea what property of what object the system is changing to visually move the view. Perhaps if I knew what object/properties to examine I could deduce something about what is going on. Can anybody tell me how the system might be doing this?
The answer is that the "view" that was being moved was being presented by a UIPageViewController and I wasn't thinking about that. The UIPageViewController is presenting the child views inside a UIScrollView (subclass, I think) and so the scroll insets for the containing UIScrollView were being changed by iOS 7. Since the whole "presenting as a child" thing actually works -- of course I couldn't see the problem in the child view.
The "fix" was to un-check the View Controller setting for Adjust Scroll View Insets for the UIPageViewController view.

Strange UIContainerView autolayout rotation behavior

I have a view controller that consists of two child UIContainerViews, one of which is fixed-width, and the other which dynamically adjusts its width based on portrait vs landscape mode. Both contain UITableViews.
For some reason, if the screen loads in portrait orientation it renders fine. However if it loads in landscape and then rotates to portrait, the second (dynamic) table gets all screwy and thinks it's wider than it is/should be, letting you scroll it horizontally which it should not be doing.
The cells of the tableView all size properly and stick to the left side of this "too wide" tableView. However, if I color the background of the tableview pink, I can see the whole thing is extending into this too wide zone.
It's baffling me what's going on here. Shouldn't autolayout be the same regardless of which orientation the view controller was loaded as?
If I log the widths in a viewDidRotate... handler, everything appears to have the correct width, yet it's still rendering in this bizarre way.
Is there perhaps I way I can just force the container view to re-lay it self out?
Update: It is the contentSize of the tableView that is getting messed up. All the other widths are correct when logged, but the contentSize.width is way off. The good news is I can just manually set this back to what it should be and everything works great! However it doesn't answer the question as to why it's happening in the first place or what I'm doing wrong (if anything).
Here's a screenshot:
In order to force a UITableView to (re)calculate its content size, you may use layoutIfNeeded method. It traverses through the view hierarchy and lays out the subviews immediately, so content size is set properly.
Instead of setting the size manually, call this method for your table view.

iOS: Drawing a Rectangle on an ImageView and Adjusting Borders

Attached 1 is a screenshot from an app called GeniusScan where you can photograph any document and an adjustable rectangular grid shows on the imageview. You can easily adjust the borders of the grid with your fingers to select the portion of the image that you want to scan. It will be then transformed into the correct prospective.
1- How can I draw and interact with the grid on the imageview?
2- How can I return the corner points of the grid to my view controller.
Update: I found a wonderful class called BJImageCropper which allows to use fingers to ajust the borders, but only for a box like rectangle. Can anyone suggest how it can be updated to support shapes like in the GeniusScan app?
Dude:
I created a demo that solves both questions:
1- How can I draw and interact with the grid on the imageview?
By Adding 4 views that will act as interactive control points by adding UIPanGestureRecognizer and then drawing the grid using CAShapeLayer on top of your view.
2- How can I return the corner points of the grid to my view controller.
You must keep references to the four control points of your grid.
Here's the link to my code.
This isn't actually drawing on top of UIImageView. It's actually an overlay (view) on top of the UIImageView. You need to keep track of 4 points (have 4 views as subview of the layer), track their positions, once moved, use drawRect: to draw lines based on the 4 points.
The way I've implemented it in my app is, I overlay the UIImageView with a transparent 'SelectionView' (a custom view that I wrote). The selectionView contains 4 custom subview of class 'Vertex'. The vertex talk back to the selectionView via protocol method every time user touches/moves it (it's actually not important which vertex moved, just that it moved):
- (void)vertexMoved:(Vertex *)vertex;
Then the selectionView knows that it needs to re-draw, so it calls setNeedsDisplay which calls internally calls drawRect (you should never call drawRect) where I do the actual drawing of the bounding rect. Basically, iterate through each vertex and draw a line using Quartz APIs and fill it with semi transparent/hollow color.
This is how I am doing it atleast, I am sure there are other ways.

How can I scroll a UIView of indefinite size within a UIScrollView

I'm trying to draw a graph that is indefinitely large horizontally, and the same height as the screen. I've added a UIScrollView, and a subclass of a UIView within it, which implements the -drawRect: method. In the simulator, everything works fine, but on the device, it can't seem to draw the graph after it reaches a certain size.
I'm already caching pretty much everything I can, and basically only calling CGContextAddLineToPoint in the -drawRect: section. I'm only drawing what's visible on the screen. I have a delegate to the UIScrollView which listens for -scrollViewDidScroll: which then tells the graph to redraw itself ([graphView setNeedsDisplay]).
I found one method that tells me to override the +layerClass method and return [CATiledLayer class]. This does allow the graph to actually draw on the device, but it functions very poorly. It's incredibly slow to actually draw, and the fade in that occurs is undesirable.
Any suggestions?
Well, here's my answer: I basically did something similar to how the UITableView works with cells: I have an NSMutableSet of GraphView objects which store unused graphs. When a section of the scroll view becomes visible, I take a graph view from that set (or make a new one if the set is empty). It already had a scrollX property to determine which part of it was supposed to draw. I set the scrollX property to the correct value and, instead of using the screen width, I gave it an arbitrary width to draw. When it goes out of the scroll view, it is removed from the UIScrollView and added to the set.
I wonder though if I really even need to remove them when they go outof the view? It may be prudent to try leaving them in and remove the ones not on screen only if I get a low memory warning? This might get rid of the pause whenever it needs to redraw a section of graph that hasn't changed.
My saving grace here was that my GraphView already was set up to draw only a portion of the graph. All I needed to do then was just make more than one of them.
I think this is a limitation of the iPhone graphics hardware. Through experimentation, I have seen that the iPhone will refuse to draw a frame that is bigger than 2000 pixels in either height or width. It probably has something to do with limited size for frame buffers in hardware.
Watch the 2011 WWDC session video entitled "Session 104 - Advanced Scroll View Techniques".
Thanks, that's helpful. One question -- what did you use for the contentSize of the UIScrollView? Does UIScrollView tolerate large content sizes (over 2000 px) as long as you're not creating buffers to fill the entire space in your content view? Or are you keeping the UIScrollView a constant size (say, 2 screen widths) and resting the UIScrollView contentOffset property each time you draw (using scrollX instead of the contentOffset to store your position)?
I think I answered my own question (the latter seems like a better alternative), heh, but I'll go ahead and post this in case other people need clarification.