Why is dismissing uinavigationcontroller not releasing view controller memory? - uiviewcontroller

Implementing UIDocumentBrowserViewController in an existing app. This vc, in iOS 11 is the root view controller, and tapping a file creates my doc, instantiates my view controller, and presents it inside a UINavigationController. It all works, in that the files display, the proper document opens, the vc displays and works as expected. I had to add a left button to the nav bar to provide a way to close the doc/vc.
When I tap the "Done" button, the view controller closes and returns to the document browser. All of that is good.
The problem is that the view controller's memory isn't releasing (and the domino effect of document memory, etc then not releasing). In the iOS 10 side of things, with a UICollectionViewController embedded in a UINavigationController as the initial vc, but the doc and the display vc identical code for iOS 10 & 11, all memory releases. I've studied How to correctly dismiss a UINavigationController that's presented as a modal? and related posts, tried dozens of alternatives, and am just not seeing what I'm missing. Instruments isn't showing any memory leaks, though I see document objects in memory after dismissing the view controller. The log shows that the vc's viewWillDisappear is being called at the proper time.
I appreciate any insights into why the vc memory isn't being released (deinit() not being called).
Thank you.
#available(iOS 11.0, *)
class DocumentBrowserViewController: UIDocumentBrowserViewController, UIDocumentBrowserViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
allowsDocumentCreation = true
allowsPickingMultipleItems = false
}
// MARK: Document Presentation
func returnToDocumentBrowser(sender: UIBarButtonItem) {
print("returnToDocumentBrowser")
if let controller = self.presentedViewController as? UINavigationController {
controller.dismiss(animated: true, completion: nil)
}
}
func presentDocument(at documentURL: URL) {
print("present document")
let doc = MyDocument(fileURL: documentURL)
doc.open(completionHandler: { (success) in
if (success) {
print("open succeeded")
DispatchQueue.main.async {
let myController = self.storyboard!.instantiateViewController(withIdentifier: "my controller") as! MyViewController
myController.navigationItem.setLeftBarButton(UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(self.returnToDocumentBrowser(sender:))), animated: false)
myController.doc = doc
let navigationController = UINavigationController(rootViewController: myController)
self.present(navigationController, animated: true, completion: nil)
}
} else {
print("open failed")
}
})
}
}

The navigation stack seems at the heart of this. In the iOS 10 version, a collection view controller is the root view controller and is embedded in a navigation controller via storyboard.
In the iOS 11 version, the document browser view controller is the root view controller and cannot be embedded in a navigation controller. When I presented in the original code, my document view controller became the navigation stack's root view controller, and that cannot be popped, for example.
So I figured that maybe the navigation controller needed a different root view controller to mimic the prior behavior as much as possible and require the fewest code changes.
So I changed
let navigationController = UINavigationController(rootViewController: myController)
self.present(navigationController, animated: true, completion: nil)
to
let dummyVC = UIViewController()
let navigationController = UINavigationController(rootViewController: dummyVC)
from.present(navigationController, animated: true, completion: nil)
dummyVC.navigationController?.pushViewController(myController, animated: true)
This nav controller setup added a back button, meaning I didn't have to add a back button, and that handled popping the view controller automatically. As a result, I only needed to dismiss the nav controller, since it wasn't the app's root vc.
Finally, for the comment that the delegate should be declared weak, that's Apple's doing, and I just use it as provided. I'm not sure what affect that's having on things.

Related

go to the previous local html in UIWebView in iOS

In my project there are plenty of html files which through anchor links are connected to each other.
All of the html files are shown after selected in a UITableView, by an UIWebView. Once loaded the anchor links work and user can go to the chosen html.
Now the problem arises when want to go back, since whatever i do the back button in the navigation bar takes us to the tableView not the previous html.
How can i add a back button and how do i know that at any given time which html is being seen through UIWebView ?
import UIKit
class DisplayViewController: UIViewController, UIWebViewDelegate {
var articleName = “”
#IBOutlet var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
functionOfWebView()
}
func functionOfWebView()
{
let URL = Bundle.main.url(forResource: "\(articleName)", withExtension: "html")
let request = NSURLRequest(url: URL! as URL)
webView.loadRequest(request as URLRequest)
}
You can easily achieve this by linking the webView's "goBack" action to a UIButton.
I assume that you are navigating between the html files in the same webView.
From the storyboard, select the webView and then select "Connections Inspector":
Note that there is "goBack" option in the list of "Received Actions"; Drag from its circle to a button:
Now, instead of popping the current ViewController, the button should do the desired functionality to your case (back to the previous webpage in the webview).
In my case i did a VC's property isInitialWebPageLoaded which reflects whether or not webView is loaded from HTML string or not - which implicates that user did tap link or something else happened.
To get know when it happened VC need to conform to UIWebViewDelegate protocol and implement func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool like that:
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
isInitialWebPageLoaded = navigationType != .linkClicked
return true
}
After that with every back button action is being invoked i simply check:
if webView.canGoBack {
webView.goBack()
} else if isInitialWebPageLoaded == false {
webView.loadHTMLString(yourHTMLString, baseURL: nil)
} else {
navigationController?.popViewController(animated: true)
}
Hope it helped.
You must to add an unique ID in the HTML and later when go back with "goBack" action parse the html finding the ID .

UIMarkupTextPrintFormatter never renders base64 images

Im creating a pdf file out of html content in swift 3.0:
/**
*
*/
func exportHtmlContentToPDF(HTMLContent: String, filePath: String) {
// let webView = UIWebView(frame: CGRect(x: 0, y: 0, width: 694, height: 603));
// webView.loadHTMLString(HTMLContent, baseURL: nil);
let pdfPrinter = PDFPrinter();
let printFormatter = UIMarkupTextPrintFormatter(markupText: HTMLContent);
// let printFormatter = webView.viewPrintFormatter();
pdfPrinter.addPrintFormatter(printFormatter, startingAtPageAt: 0);
let pdfData = self.drawPDFUsingPrintPageRenderer(printPageRenderer: pdfPrinter);
pdfData?.write(toFile: filePath, atomically: true);
}
/**
*
*/
func drawPDFUsingPrintPageRenderer(printPageRenderer: UIPrintPageRenderer) -> NSData! {
let data = NSMutableData();
UIGraphicsBeginPDFContextToData(data, CGRect.zero, nil);
printPageRenderer.prepare(forDrawingPages: NSMakeRange(0, printPageRenderer.numberOfPages));
let bounds = UIGraphicsGetPDFContextBounds();
for i in 0...(printPageRenderer.numberOfPages - 1) {
UIGraphicsBeginPDFPage();
printPageRenderer.drawPage(at: i, in: bounds);
}
UIGraphicsEndPDFContext();
return data;
}
Everything is rendered fine except my base64 encoded images. The HTML content itself in a webview or inside safari or chrome browser is presented correctly and is showing all images correctly. But the images are never rendered into the pdf.
Why are the images not rendered and how can I get them to be rendered?
This happens because WebKit first parses the HTML into a DOM, and renders content on multiple event loop cycles. You therefore need to wait for not just the page DOM to be ready but for the resource loading to be complete. As you also suggest, you need to refactor your code such that the webview gets loaded first, and you only then export its contents.
To determine the correct time to fire the export, you can observe for the state of the DOM document in the web view. There are multiple ways to do this, but the most readable option I find is a port of an answer to a related Objective-C question: in your UIWebViewDelegate implementation, implement webViewDidFinishLoad in the following way to monitor document.readyState:
func webViewDidFinishLoad(_ webView: UIWebView) {
guard let readyState = webView.stringByEvaluatingJavaScript(from: "document.readyState"),
readyState == "complete" else
{
// document not yet parsed, or resources not yet loaded.
return
}
// This is the last webViewDidFinishLoad call --> export.
//
// There is a problem with this method if you have JS code loading more content:
// in that case -webViewDidFinishLoad can get called again still after document.readyState has already been in state 'complete' once or more.
self.exportHtmlContentToPDF(…)
}
I found the solution!
The export to PDF happens before the rendering process is finished. If you put in a very small picture it is showing up in the PDF. If the picture is too big the rendering process takes too much time but the PDF export isnt waiting for the rendering to finish.
So what I did to make it work is the following:
Before I export to PDF I show the Result of the HTML in a WebView. The WebView is rendering everything correctly and now when I press on export to PDF the PDF is showing up correctly with all images inside.
So I guess this is a huge lag that there is no way to tell the PDF Exporter to wait for the rendering process to finish.

Swift 2 - Real-time JSON feed

In my app I have a UICollectionView that get data from a server and shows cells with an image and user name and comment.
Every time a user posts a new image and comment a new cell is created. Right now, the UICollectionView shows the new feed just if I reload the view 2 times.
I was wondering if there is a way to update the data in the user’s interface as it changes. Without refreshing the app actually like Facebook app does?
i have been updating like this
override func viewDidLoad() {
super.viewDidLoad()
var timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "callbackFunction", userInfo: nil, repeats: true)
}
internal func callbackFunction() {
// do your busines logic
}

Trouble sending info between view controllers with modal presentation

I am having trouble sending info from one viewcontroller to the next. In my storyboard I am using a modal presentation. When I write the destinationviewcontroller is when I get the error. If I do not write that line the code works ok. Any ideas?
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "segueEditItem") {
// if I erase the next 2 lines the code will run ok
let ss = segue.destinationViewController as EditItemViewController
ss.toPassID = toPassID
}
}
Try out:
let ss = (segue.destinationViewController.visibleViewController as EditItemViewController)
I think you already set in your Storyboard Settings or your segue the presentation to "Modal" ?

Change view when didReceiveRemoteNotification

I am trying to change from the App delegate method to the Master View when I receive a Remote Notification, in order to perform a segue in the Master View to another view, but I am getting an NSInvalidArgumentException
Code in App Delegate when didReceiveRemoteNotification:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
EmergencyMasterViewController* maincontroller = (EmergencyMasterViewController*)self.window.rootViewController;
[maincontroller alert];
}
Code in MasterView:
-(void)alert
{
[self performSegueWithIdentifier: #"Warning" sender: self];
}
And the error I am getting: [UINavigationController alert]: unrecognized selector
It is because your window rootViewController is actually a UINavigationController instead of your EmergencyMasterViewController. You need to check how you assign the window root view controller in your app delegate didFinishLaunchingWithOptions or something similar.
Try to get the view controller embedded in the navigation controller, for example:
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
NSArray *viewControllers = navigationController.viewControllers
EmergencyMasterViewController *maincontroller = [viewControllers objectAtIndex:0];
It might be safer for the UINavigationController to pop to root view controller first before you try to get the EmergencyMasterViewController, in case the user is already navigating his way through the navigation stack:
[navigationController popToRootViewControllerAnimated:NO];