Preloading mathjax into a collection iOS Swift - ios

(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.

Related

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()
}

iOS Photo Gallery Memory Leak / Crash when using Push Segues

I've created a small photo gallery which is presenting a new view controller with a larger version of the photo and some additional text when it is clicked:
The problem is - after going through a handful of images - the application crashes due to overuse of memory. I attempted to resolve this by compressing the images in order to leave a smaller memory footprint, but the issue remains and I'm not sure what else I can do to resolve this issue.
Also - there is almost no code to doing this since I'm using storyboard's push segues as well as the built in navigation item to go back between viewControllers.
P.S.
If you feel source code is necessary to provide insight in this instance - it can be found here:
https://www.dropbox.com/s/q1qq8pq4tzv8wyo/EXAMPLE%20BUILD.zip?dl=0
To resolve this issue you have to use this trick; Put a "placeHolder" image in your cell's imageview in "StoryBoard". Don't load the images all at once in your "ViewController", load them one by one by running a loop or in your "cellForRowAtIndexPath()" method and add a delay in each iteration (Load first image then add a delay, load second image and add a delay, then for third one and so on up to the last image).
If you want to know how to add a delay then check this link:
NSTimer - how to delay in Swift
To resolve this issue I simply resized the images - I noticed I accidentally used a gigantic (6000 x 4000) image and even though I compressed the images iOS had to crunch pretty hard to resize them into the view... thereby causing the memory leak and subsequent crash.
Resizing them to 600x400 did the trick.

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.

App crashes on return from some share plugins of activityViewController

I've got a TableViewController with static table; one of it's cells houses an UIView named graphArea. The view renders a chart, it's background and an axis line - all inside it's drawRect(). There are also two another views (sunView & markerView), that are made with Interface Builder and used for chart dynamics (moving marker line and point on touch events).
All worked buttery smooth until I've implemented and tried to test a share button, that employs the ordinary activityViewController mechanism.
The magic begins, when one from a couple of share activities, whose share plugin window takes the full screen, is finished (no matter whether sharing succeeded or cancelled). The app crashes.
Discovery using debugger made apparent to me, that the crash happens, because some views, including graphArea, sunView, markerView are nil after return from sharing screen.
Only some of fullscreen share plugins (like preinstalled Mail and Messages, or, in my case, "Download to DropBox" action) lead app to crash. Other fullscreen share plugins do not (tested Telegram, WhatsApp, Skype). No one of those non-fullscreen plugins has ever caused crash (Evernote, Twitter, 2Do etc.).
It looks like graphArea, sunView, markerView are deallocated from memory when "malicious" share plugins take full screen. I haven't figured out, why.
Here's some debug info:
The traceback and assembly of fatalErrorMessage.
The next screenshot shows a part of controller code and properties, that are nil on return from share plugin (gray selection). And yes, they were all non-nil before.
Please, help me! Thank you in advance!
Thank you, Palpatim. My friend also pointed me at the same thing: I've put graphArea.removeFromSuperview() in viewDidDisappear(), and this caused the exception after share plugins, that have .presentationStyle = fullScreen. So at the point, when the app is to show again, there is no more graphArea in on the tableView.

Suspend redrawing while changing multiple children

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.

Resources