Suspend redrawing while changing multiple children - ios

I have a UIViewController that after asynchronously loading some data needs to resize some child views (Some UITableViews, and some UIScrollViews). This works just fine in the simulator, but on an actual device, it hangs for a long time (as much as 30 seconds in one case). I think the problem is that it wants to recalculate everything after each changed to the child view and I would like to be able to tell it to defer any recalculations until I've resized everything, but I'm drawing a blank on finding a way to do that. Is there a mechanism to do this?
So what I'm doing is something like this (I'm using C# with Xamarin but input in Obj-C would also be appreciated!):
// Need to suspend layout here...
MyTable.Frame = new RectangleF(...);
SomeOtherTable.Frame = new RectangleF(...);
ScrollView.ContentSize = new SizeF(...);
AnotherScrollView.ContentSize = new SizeF(...);
AnotherScrollView.Frame = new RectangleF(...);
// Ok - now you can redraw!
Update: Jacob's suggestion of disabling animation didn't seem to help, but I think I've isolated the problem to the table resizing. They seem to have the biggest impact on performance, but I'm not sure why, or how to mitigate the problem. I may follow up with a separate question on that.

Related

Xcode 13 iOS 15 Programmatic Obj-C Constraints translatesAutoresizingMaskIntoConstraints

I use 100% programmatic constraints in my interface code. Upgraded to Xcode 13 and iOS 15. Got tons of NEW execution warnings about constraints, all saying that the system had to break some constraints to comply with others. I had not seen such warnings for several years, and have not touched my constraint code in all that time. Yet thorough testing shows my code continues to run correctly. What's up?
Answer: I had been a bit cavalier with the timing of when iOS actually calculated the constrained dimensions. I put all of my programmatic interface declarations into a single method. At the bottom of that method, I have long had code that went beyond interface layout, into NavCon preliminaries. Among those NavCon declarations, I had lines like:
self.view_D0_Tutorial.frame = self.view_CenterPane_D0_Tutorial.frame;
I noted at the time that I wrote and debugged those lines, that using the debugger to ask what the location data was, all I ever got was CRect (0,0,0,0), yet the code somehow did the right thing.
Well, in the upgrade, iOS apparently changed the way they do things. I had to do two things in response.
(1) moved all the NavCon preliminary code to a new separate method, and call it with performSelector and 0 delay.
(2) changed the simple frame assignment to a more limited assignment:
self.view_D0_Tutorial.frame = CGRectMake(0, 0, self.view_CenterPane_D0_Tutorial.frame.size.width, self.view_CenterPane_D0_Tutorial.frame.size.height);;
The combination made the error messages go away, and my code continues to run correctly!

SwiftUI | Warning: Bound preference _ tried to update multiple times per frame. Possible reasons?

Ever since I am working with preferences (PreferenceKey,..), I receive this message in the console:
Bound preference _ tried to update multiple times per frame.
After countless times of research, I haven't found any way to silence it. So... since there is yet no question specifically to this warning, what would you think are possible reasons?
If not, can this warning be ignored or did I have to fix it?
Thank you so much!
(I have tried to find an example, but somehow I didn't get any warnings with an easy one...)
The SwiftUI change handlers such as onPreferenceChange are called on an arbitrary thread. So if these changes impact your View, you should re-dispatch to ensure that you make those updates on the main thread:
.onPreferenceChange(MyPreferenceKey.self) { newValue in
DispatchQueue.main.async {
widget.value = newValue
}
}
I think this answer from an Apple engineer describes the general issue:
It sounds like you have a cycle in your updates. For example, a
GeometryReader that writes a preference, that causes the containing
view to resize, which causes the GeometryReader to write the
preference again. It’s important to avoid creating such cycles. Often
that can be done by moving the GeometryReader higher in the view
hierarchy so that its size doesn’t change and it can communicate size
to its subviews instead of using a preference. I’m afraid I can’t give
any more specific guidance than that without seeing your code, but
hopefully, that helps you track down the issue!
https://www.bigmountainstudio.com/community/public/posts/65727-wwdc-2021-questions-answers-from-slack-the-unofficial-archive
At least it inspired me to solve the related bug in my case and make the warnings go away (almost :)).
I am building a calendar app, based on the month it set the choseDate as initial value, when not using main queue, it goes into an infinite loop with messages like onChange(of: String) action tried to update multiple times per frame. After change to use main queue solved my problem.
DispatchQueue.main.async {
dateChosen = Date()
}

UICollectionView & AVPlayer Performance

I have a UICollectionView whose cells each have an AVPlayerLayer, all playing a video at the same time. About 9 cells fit on the screen at a time and it's pretty laggy so I need ways to boost the performance to achieve smooth scroll. There's a few things I've tried already:
1) Each cell has only one instance of AVPlayer. It doesn't get created, instead, player.replaceCurrentItemWithPlayer is called when the video url changes.
2) Because I'm using ReactiveCocoa, it's trivial to skip repeat urls to avoid playing the same video twice.
What else can I do to speed up the scroll and performance?
First I want to say it's a bit crazy to see several players in action at the same time: it's a heavy task of rendering anyway.
As far as I know, simply scaling/re-framing the video to smaller size doesn't make it less content to render: if you have a 100x100 picture to render and you render it in a frame of 10x10, it still consumes the same amount of memory as the original picture would consume; same thing goes for videos. So, try to make your video assets having similar resolution as the frame where you would present them.
Store each cell in an NSCache or NSMapTable using the NSIndexPath as the key for each; then, whenever you need a cell, call one directly from the cache or map table.
Obviously, you'll have to create all the cells at once, but you'll get the same scrolling performance as you do with nothing in the cells at all.
Need sample code?
Here's my latest iteration of a perfectly smooth-scrolling collection view with real-time video previews (up to 16 at a time):
https://youtu.be/7QlaO7WxjGg
It even uses a cover flow custom layout and "reflection" view that mirrors the video preview perfectly. The source code is here:
http://www.mediafire.com/download/ivecygnlhqxwynr/VideoWallCollectionView.zip
Unfortunately, UIKit will hit bottlenecks and drop frames when put under pressure for this use case. If you can muster refactoring code to use Facebook's AsyncDisplayKit / then this would be your best bet.
For objective-c / this project here is a good reference.
https://github.com/TextureGroup/Texture/tree/master/examples/ASDKTube
- (ASCellNode *)tableNode:(ASTableNode *)tableNode nodeForRowAtIndexPath:(NSIndexPath *)indexPath
{
VideoModel *videoObject = [_videoFeedData objectAtIndex:indexPath.row];
VideoContentCell *cellNode = [[VideoContentCell alloc] initWithVideoObject:videoObject];
return cellNode;
}
there is swift code available if you dig deep enough in github.

Preloading mathjax into a collection iOS Swift

(Please excuse the amount of text)
I'm trying to render a bunch of math questions in an app. Say there's 30 questions, and each question has about 7-8 fields (instructions, question, answer choices, and review).
I am able to do this successfully when I load them into containers with MathJax rendering (I also use a callback in the JS to make sure the content is loaded before being viewed).
The thing is, the process takes a few seconds each time, and I'd like to preload the entire set of questions, and the views, so that after the initial load, question loading will be instantaneous.
I think using a collection would be the best way to do this, and I've been trying two methods with no luck:
load the collection with all the content as cells/items, then jump to the cell (I was facing the issue that only the viewed cell would load, and then I figured if they did all load, I would face the issue that I am facing now with the next option)
load all the content into an invisible subview, then load the subview into a cell when it is selected (may offer more control over how things are loaded)
In either case, I run into the same issue (and this even happened in the initial one-by-one loading with multiple containers, where if I jumped around too quickly, the app would crash).
When loading a bunch of subviews, each with Mathjax, the app gets overwhelmed and crashes (I believe due to memory or cpu, it says "lost connection to iPad"). This doesn't seem to happen if there is a lot of Mathjax within one view. I think it's because the processes don't wait for each other to finish before beginning, and the app can't handle running multiple Mathjax rendering at the same time.
I think a possible solution is to make sure that the loop that loads the view does not reloop until the Mathjax is done rendering, but cannot figure out a way to make the loop wait (once it initiates the loading it keeps incrementing).
Hopefully someone can provide a good idea for:
how to structure the preloading mechanism in general
or
how to specifically make the loop wait for the view to render before relooping (I have a process that recognizes when the Mathjax has been fully rendered, but I can't figure out how to make the loop wait for it. This process is currently used to simply unhide the view once it's done).
for var i = 0; i < objectsKeyValues.count; i++ {
let iInput:String = cDParentVC.string1 + cDContentAbstraction.objectsKeyValues[i]["instructions"]! + cDParentVC.string2
let qInput:String = cDParentVC.string1 + cDContentAbstraction.objectsKeyValues[i]["question"]! + cDParentVC.string2
... for a few more of these
let iView = WKWebView.init(frame: CGRectMake(5,5,300,35), configuration: config)
...same
iView.loadHTMLString(iInput, baseURL: NSBundle.mainBundle().bundleURL)
..same
viewDictArray.append(["iView":iView,"qView":qView,"a1View":a1View,"a2View":a2View,"a3View":a3View,"a4View":a4View,"a5View":a5View,"rView":rView,"pView":pView])
cDLargeContent.invisiView.addSubview(viewDictArray[i]["iView"]!)
...same
}
I suspect NSOperations or GCD could also provide the necessary controls, but I am pretty green with programming and haven't been able to implement those concepts successfully, let alone verify if they would solve this issue.

iOS7 dirty rect drawing weirdness

I have been developing a cad-like drawing app for iOS, which makes careful use of dirty rect clipping to make for a smooth drawing experience - it has worked quite well for months. Now, I know iOS doesn't automatically clip dirty rects, but CoreGraphics will. For reference, here's what the preamble of my main view's drawRect function looks like (leaving out the actual drawing mechanism)
- (void)drawRect:(CGRect)dirtyRect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
if ( !CGRectIsNull(dirtyRect))
{
CGContextClipToRect( context, dirtyRect );
}
CGContextConcatCTM( context, self.viewTransform );
.... drawing here, all done by CG* vector and bitmap functionsa
CGContextRestoreGState( context );
}
Now, this code worked beautifully for a long time, iOS6, early iOS7 builds, etc. Smooth as butter. But just recently, I've been seeing a VERY odd set of behaviors, and I'm curious if anybody here has had similar experiences.
First, I trigger redraws the normal way, setNeedsDisplay when I need to redraw the whole view, and setNeedsDisplayInRect passing a dirty rect for just the small bit that needs redrawing. Weirdly, recently when I call setNeedsDisplayInRect the rect I pass is ignored, and instead drawRect receives a rect matching the view's bounds and not the intended subrect. I only noticed this when testers reported dramatically bad drawing performance compared to previous builds - I've been using the simulator too much recently and hadn't noticed :/
I suspected this might be the result of some kind of multiple-dirty-rect union, so I overloaded setNeedsDisplay and setNeedsDisplayInRect, tracing when they got called, but that let nowhere. So then I decided to track my dirty rect manually as an iVar in the my rendering view class. Here's where the second oddity shows up: when I clip to the now correct dirty rect, the contents outside the dirty rect are cleared.
They should NOT be cleared. I have explicitly set self.clearsContextBeforeDrawing = NO, so I don't understand why the behavior I'm seeing is happening, particularly when it wasn't happening in the past.
Now, I'm probably going to do a git-bisect style of debugging to see if I can find a version way back when which didn't have this bug. But I'm hoping somebody here with CoreGraphics experience can lend me some conceptual support. This has me totally baffled.
P.S. I haven't touched the drawing code for my app in a long time since I've spent the last few months developing the app around the drawing tools. This is why I suspect a change to iOS7's drawing pipeline may play some kind of role here.
I think you need to include this line in your code inside drawRect method :-
[super drawRect:dirtyRect];
For anybody who stumbles across this in the future when searching for help with iOS dirty rects, I want to say I solved this, and it was my bug, not apple's. I spent a morning doing a git bisect to narrow down when the trouble showed up, and found that when I did some refactoring back in early october, I introduced some change notifications to my model which were indirectly causing a setNeedsDisplay which overwrote the setNeedsDisplayInRect my touch handling code was laboriously computing.
So - not an apple bug!
drawRect:dirtyRect is for mac. For ios you should use drawRect:rect. It should be called with setNeedsDisplay.

Resources