WKWebView crashes at 30M of RAM usage on iOS 9 - ios

I have a WKWebView that loads a custom HTML file from our server. In our app there's a button to change the web page's font size using JavaScript by iterating through all elements and set their style property(this should be irrelevant since on other iOS it works perfectly).
On iOS 9 devices, when the font gets larger and larger, the webview gets progressively slower to respond to the font change. For example, changing from 22px to 24px takes 2s to take effect, and changing from 24px to 26px can take 3s or more. Eventually, when the font size gets to 28px, the webview would crash the app with a memory warning in the console. If not connect to Xcode, however, it would not crash but turn into a blank screen as described in this similar post.
This is not a duplicate since I'm asking for something different:
I have implemented webViewWebContentProcessDidTerminate and can confirm it gets called, but when I try to reload the webView it simply crashes again and fall into a crash-reload-crash loop. The Xcode panel shows my memory is using 31M of RAM, which like 5% for my iPhone 4S, so this shouldn't be a true memory issue. The web page consists of paragraphs of texts only, so is this a system bug or something that I'm doing wrong? Please comment if you need more details.
Here's my code:
class ReaderController: UIViewController {
// loads webView, injects JavaScript, handles button, etc.
func loadData() {
let html = // get html
webView.loadHTMLString(html, baseURL: htmlURL)
}
}
extension ReaderController: WKNavigationDelegate {
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
loadData()
}
}

Related

PencilKit clearing canvas not always working

I am building a PencilKit app that interprets a canvasView.drawing's size. Drawings below a certain size are interpreted as tap gestures and get cleared in the interpretation process.
Effectively, I am doing it like this:
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
// Check if drawing is empty,
// otherwise this would loop
guard !canvasView.drawing.bounds.isEmpty
else {
return
}
if canvasView.drawing.bounds.width < 10 &&
canvasView.drawing.bounds.height < 10
{
canvasView.drawing = PKDrawing()
handleDetectedTapGesture()
}
}
On my 2020 iPad Pro, this works flawlessly. On other iPads however, I observe a strange behaviour:
In a series of drawing little specks on the canvas that are all supposed to be cleared immediately, sometimes the canvas seems to not be cleared, a single speck stays visible. After I draw the next speck somewhere else, the clearing is functional again, which means the canvas was internally emptied, the visible state was just not up-to-date.
Finger input on the canvas is deactivated. The canvas tool is set to pencil, width 5.0.
Does anybody have an idea what could be the reason for this behaviour?

IOS 9 native print preview doesn't render

I have built an app using the IOS 9 SDK.
I am printing using the UIPrintInteractionController and a subclass of UIPrintPageRenderer.
When the print dialog is displayed on the iPad or iPhone, UIPrintPageRenderer's drawPageAtIndex:inRect method is not called and the preview area on the dialog is just a gray box with a button that says Page 1.
This is an app that was originally written in the IOS 6 era and I am trying to update it for IOS 9, so perhaps I need to improve something.
Does anyone know what is happening here?
Thanks!
I don't understand what's happening exactly, but I had the same problem and fixed it by
1) ensuring that logic in my overrides is being executed on the UI thread
2) moving my AddPrintFormatter call to the constructor method instead of calling from a NumberOfPages override (which allowed me to delete this override property).
There seems to be some weird threading behavior now when overriding UIPrintPageRenderer (at least in Xamarin's implementation).
For example, adding this simple override causes a UIKit Consistency error for me:
public override nint NumberOfPages
{
get
{
return base.NumberOfPages;
}
}
So it seems the base method begins execution on the UI thread when there isn't an override, but the subclass's override begins execution on a worker thread.
The iOS9 SDK Release Notes document says:
"Apps that subclass UIPrintPageRenderer or UIPrintFormatter to draw content for printing must be built with the iOS 9 SDK for the preview to display. The behavior of UIPrintPageRenderer has been updated to call drawPageAtIndex:inRect: multiple times with potentially different page sizes and margins. Various methods on UIPrintPageRenderer may be called from a non-main thread, but never from multiple threads concurrently."
Turns out I was missing the numberOfPages override in the UIPrintPageRenderer derived class. The preview works after adding this override.
I'm giving carchase an upvote since the post tangentially mentioned the numberOfPages method, but my actual problem was not as subtle as suggested by carchase's post ;-).
It sounds like you need to add the print formatter.
[myRenderer addPrintFormatter:viewFormatter startingAtPageAtIndex:0];

Update webview page number when updating font

I'm working on a book reader iOS application and using webview for displaying epub books. The 3rd party library I'm using for parsing epub book is "KFEpubKit".
I've to give option to user to adjust the font size (as in iBooks), and I've achieved that feature by updating the font of webview as:
let textSizeRule = "document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '\(fontPercentage)%'"
self.webView.stringByEvaluatingJavaScriptFromString(textSizeRule)
The problem I'm facing is that by increasing/decreasing font size, page number of book must also change. But I'm unable to do so.
How can I achieve this functionality in the way other book reading applications are doing it (i.e. kindle, iBook etc)?
You can create a second WebView object which is hidden. You do the same thing ("For time being, i'm reloading the book and computing page number.") in the background webview.
In your webViewDidFinishLoad method, check which webview is loaded
func webViewDidFinishLoad(webView: UIWebView) {
if webView == backgroundWebview {
//Compute number of pages
} else {
//Foreground webview
}
}
So the user is not disturbed by a reloading webview.
Hope this helps!

UIWebView with dynamic height leads to memory crash

I've a native iOS screen with UITableView inside that displays some article. Some cells in this table display article image, title, author, comments, etc. But there is a single cell with UIWebView inside that displays article content. This cell has dynamic height depending on the content size. Article content goes from the server as html string in JSON response and may contain images, videos and other things that supports HTML format. I can edit this string using regular expressions depending on some requirements (for example increase font size depending on app settings). Here is an image representing my UI structure:
The problem is that once the article content is very large, UITableView cell with UIWebView inside becomes also very large in height and this leads to memory crash. In my case this crash happens only on iPhone 6 Plus. On all of the other devices including iPhone (5, 5S, 6), iPad (2, 3, 4) (and probably other devices that supports iOS 7) app works correctly. As I suspect the reason is that iPhone 6 Plus has a high resolution screen and only 1 Gb of memory. So rendering the same content with the same amount of memory as in other devices, but in larger resolution, leads to memory crash.
I've created two test applications with UI as in the image below:
Both apps load the same HTML content in a single UIWebView. There is no other ui or logic in both apps.
In case a) all works correctly, scroll indicator appears and only visible content are rendered. When I'm scrolling fast, I can see white space that after a moment replaces with rendered content.
In case b) UIWebView stretches to fit content size. Test app is crashes (as my real app). As I suspect in this case even invisible content rendered and that leads to memory crash.
So my question is:
How can I fix this bug without scrolling inside UIWebView? Only UITableView should be scrollable
Make your UITableViewCell reusable. i-e(UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];).
one thing very important don't use [UIimage imagenamed:#"Imagename.jpg"] which leads you to a memory crash, you can use [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Imagename" ofType:#"png"]];.
I hope it might help.
You can give webview constant height. it has scrollviewer inside. otherwise it is really hard to render large content at once. if you give webview constant size(max value) white spaces will be removed too.
edit: webviews own scrollview should work fine with tableviews scrollview bu i am not sure

webViewDidFinishLoad did finish loading but didn't finish showing content on screen

I'm using webViewDidFinishLoad a lot in my app and there's something about UIWebView that really bugs me, well, actually two things.
The first, when I load new content to a UIWebView I will see for half a second the last page that was loaded to the same UIWebView what will force me to "clean" the UIWebView using something like:
[_mainWebView stringByEvaluatingJavaScriptFromString:#"document.open();document.close();"];
before loading the new content.
The second issue I have and that's the main issue for this question is that if i'll load some new content to my UIWebView and do something like this:
[_mainWebView loadHTMLString:htmlString baseURL:nil];
...
- (void)webViewDidFinishLoad:(UIWebView *)webView {
_mainWebView.alpha = 1;
}
In some cases the UIWebView will show up white for half a second before showing up the content. I'm guessing that the content is already loaded into the UIWebView and that's why webViewDidFinishLoad:webView is firing but for small html pages showing to content takes loner than the actual load. Is there any workaround I can use to avoid the blank screen that is showing for a sec or so but still save that second?
I thought about animating the alpha from 0 to 1 but that solution feels kinda lame to me.
Try adding a javascript callback so you know when the web view contents have actually loaded: Javascript in UIWebView callback to C/Objective-C

Resources