How to set a background pattern for a large UIScrollView (that scrolls with the view) - ios

Seems like a very simple problem! But I'm having great difficulty.
My ideas and attempts so far:
scrollView.backgroundColor = [UIColor colorWithPatternImage:myImage]]
Doesn't scroll with contents
Create UIView the size of contentSize (actually, larger due to elastic bounces on scroll view), and use pattern as above on this sub view of UIScrollView.
Causes massive memory hit (and crash) when UIView gets too large. See also: Large UIScrollView with background pattern fails
Similar to (2), exept I only create a UIView the size of the maximum number of repetitions of the background image that will be seen, and cleverly move the view to wherever is needed to show the correct background repetitions.
Causes this background view to shift left and right unexpectedly when animating the scroll.
Create UIView subclass and override drawRect. Use various Core Graphics techniques to draw content by hand.
Not animatable. Implementing my own contentOffset property isn't a standard Core Animation property.
Overriding drawRect on UIScrollView doesn't respect the content offset, and doesn't get called multiple times as it scrolls/animates. The rect parameter is always simply the bounds of the UIScrollView.
As with (4), except I set the bounds.origin of my UIView subclass in my setContentOffset implementation, since it's an animatable property.
drawRect doesn't seem to get called every frame.
Use CATiledLayer, as suggested in this answer: Large UIScrollView with background pattern fails. Implementation details here: http://www.cimgf.com/2011/03/01/subduing-catiledlayer/.
I really don't want the ugliness of seeing tiles asynchronously being drawn as user scrolls. It's just a simple background pattern!
This seems like the simplest thing! Why is it so hard!?

Maybe the sample code:ScollViewSuit->3_Tiling can help you. You can search it in the official docset.
This works like CATiledLayer but only use UIKit, the tile was loaded on the main thread.
And I really don't think this is a good solution.

Related

UIScrollView is using an unrelated UIViewPropertyAnimator when it shouldn't

I'm using a UIScrollView whose content is a simple UIView subclass whose size is set using auto layout and which uses drawRect: to render its content rather than having subviews of its own.
If the content (and the size) of the UIView changes then everything works as intended. The new content instantly appears and the scrollability of the UIScrollView enables/disables if the size of the content now needs / no longer needs scrolling.
However, if there's an animation going on somewhere else on the screen things no longer work as intended.
In that case the change to the UIView is animated along with that other animation.
So if that other animation takes say, 2 seconds, the new UIView content is instantly redrawn but at a scale so that it fits in the old content's size. Then it animates the content, growing (or shrinking) over a period of 2 seconds so that at the end of the animation its size is what it should be.
The "interfering" animator in question is owned by a ViewController quite a bit further up in the food chain and the UIScrollView and its content UIView are never told about the UIViewPropertyAnimator.
The problem only happens if the content change overlaps the animation. If that other animation has completed (or hasn't started yet) the content in the scroll view is updated instantly.
Does anyone have insight into this, or perhaps could suggest a way to force UIScrollView and/or its content view to always do their own thing and not tag along for the ride if some unrelated UIViewPropertyAnimator is chugging along elsewhere?
EDIT: A bit of extra info
The animation code is older and originally made use of UIView.Animate and some swipe gestures were triggering the animation. In that case even if the animation and the content size change overlap (using the exact same repro scenario), the new (scrolled content) size appears immediately and doesn't get linked to the UIView.Animate animation. Swapping in UIViewPropertyAnimator code (and without any gesture recognizers getting involved) the problem is triggered and content size change becomes animated.

How to fil rectangular ares in the screen with good performance in ios

I'm making simple number coloring game (like sandbox, pixel art, unicorn, etc) and came across a problem. I tried various methods of filling rects in the screen.
At first i created a backgroundView(a simple UIView) which had 2500 subviews(also UIViews), each one had size = (CGSize){50,50}. Added a tap gesture recognizer, detected which view should be filled, and simply changed the background color of that view. But when i placed the backgroundView in the scrollView, the scroll and zooming were awful.
Tried same thing but this time each rectangle was a CALayer. So a backgroundView, which had 2500 sublayers(each was a CALayer with 50,50 size) - zoom and scroll was also awful.
Tired to use custom drawing with overriding drawRect method in UIView subclass or drawInContext in CALayer subclass(i was filling rects with CGContext), this time problem was also caused by the zooming and scrolling. Because i needed to update every rect when user zooms, it was triggering a lot of drawRect calls, and performance was also bad.
Any thoughts how can i fill rects in the screen ?
Explanation
Yup! Adding 2,500 tiny UIViews to a scroll view can destroy performance. I wouldn't use that approach, but if you do, at least make sure that subview_N.opaque = YES; on all subviews to disable compositing.
The CALayer approach that you described is basically the same as the UIView approach because views are backed by CoreAnimation layers (see -[UIView layer]).
The best options for drawing thousands of rectangles to the screen on iOS without decimating frame rate, is to use one of the following approaches:
Draw directly using CoreGraphics
Draw directly using OpenGL (extremely involved)
Use a layout engine such as UICollectionViewLayout
Now, you said you had tried overriding -[UIView drawRect:], but you didn't provide any example code for us to checkout. Please update your question with actual code for method #3 if you want more specific feedback. It's very likely that something is wrong with the drawRect code you created. CoreGraphics can definitely draw thousands of squares on screen without dragging frame rate down that badly.
One Solution
I recently released a project, YMTreeMap, that draws thousands of rectangles into a UIView to create financial TreeMaps. It's in Swift, not Objective-C, but the underlying concepts are the same. For this, I created a custom UICollectionViewLayout that lets Apple's well tested UICollectionView class handle the nitty gritty of selective drawing, zooming and animation.
The example UIViewController code in the YMTreeMap project shows how to draw thousands of colored rectangles to the screen if all you know is their location and size. This sounds like what you are doing. Since you're also scrolling and zooming, this solution might be perfect for you because UICollectionView has native support for both of those.

wiil one very large UIView for long scrolling in UIScrollView cause resource issues?

In terms of creating an infinite scrolling capability using a UIScrollView (no looping back to the start), for a calendar in fact, will one very large UIView for long scrolling in UIScrollView cause resource issues?
Assumptions:
That is assuming one programmatically adds/removing subviews onto this main backplane view as it is about to need being shown/visible, then removing afterwards.
Don't want to use UICollectionView (to focus question on use of UIScrollView please)
Requirements:
Would want to put UI View onto this background plane as well as drawing to it directly, e.g. adding lines.
So therefore would need to be adding/removing things like Lines drawn as well UILabels. (well haven't delved into Lines and whether they would need to be drawn in their own subview, upon which you're then adding removing these "line" subviews, as opposed to drawing directly onto the main backplane view in drawrect)
Background:
I see some suggestions of having 3 "pages" (views) and you keep moving these as scrolling occurs, however this just seems more complicated.
There should not be any resource issue as far as you use views.
I developed kinda map application with the next view structure.
MapScrollView (UIScrollView)
- TileContainerView (UIView)
- TileContainerSubViews[10..] (UIView)
- TileLayer [] (CALayer)
- UIButton[]
- IconLayer[] (CALayer)
- TrackLineLayer (CALayer)
- .....
All of the Tile**Views in hierarchy and MapScrollView share the same size, which is aligned to the size of the TileContainerSubViews[n], which further holds many TileLayer(s) with size of 256x256. The largest TileContainerSubView[] can hold as many as 10000x10000 TileLayer(s)
which is 2560000x2560000 in view size. (Of course, we can't actually add that many layers due to resource issue.) All of views/layers are added/removed on the way.
Note you can create large UIView but not CALayer.
When I tryed to create huge CALayer, the program terminated with some error message. CATiledLayer doesn't seem to have this constraint but I haven't tested.

Drawing a graph within a UIScrollView

I am exploring the idea of drawing some custom primitives (using CGContext) on a view that is scrollable and larger than the phone screen width.
The idea would be to use the "power" of a UIScrollView by programmatically scrolling the content of the view as the content is added and decouple in this way the scrolling handling (and general UI interaction with the view) from the content drawing.
Is this a feasible approach in iOS?
Yes it is. The easiest approach AFAIK would be to add a UIView onto the UIScrollView. You would then draw on that UIView instance - after drawing another part of your graph/image you would need to inform the containing scroll view, via a delegate for example, that it needs to update its contentSize. This would of course be the size of the UIView upon which you drew. The update is needed, beacuse it seems that you may need to increase your drawing area size as you do it.

Fade UIImageView as it approaches the edges of a UIScrollView

I have a UIScrollView over an image at the bottom of my app that acts as a dock with icons that can be scrolled through horizontally. Instead of the harsh edges of the UIScrollView, I would like the icons to fade out for a more aesthetically pleasing look. Being new to iOS development, I don't know if either of these would be valid options:
Create a faded image to use as an overlay on the scrollview so the
icons only appear through the visible portion.
Actually change the
alpha of the images based on their distance from the center (or from
each edge).
I suspect the first idea would be the most simple, but I'd like to throw this out there for any other ideas.
Note: I did see this tutorial, however that technique assumes that the background is a solid color. If I were to do it programatically, I would probably need to fade the individual images.
You can definitely implement something along the lines of #2. It'd be something similar to what the tutorial describes. The alpha transition however won't be as smooth as using the gradient layer mentioned in the tutorial or using an image since the entire icon would have the same alpha. How much discernible the difference is depends on the size of your icons. Smaller icons, very few will be able to tell the difference. Larger icons the difference would be quite clear.
You'd have to implement the
(void)scrollViewDidScroll:(UIScrollView *)scrollView
method in your scroll view's delegate class. This method will get called every time the scroll view changes the location of its content. In this method you can call its subviews and adjust their alphas as required. To optimize it a bit instead of calling the alpha adjustments on all the elements you can just update the subviews which are still partially/completely visible.
EDIT: to figure out which views to adjust you'll use the contentOffset property of the scrollView that gets passed as a parameter in the above method.

Resources