How to remove memory leak using UIWebView? - ios

I have a UIWebView and inside that I have to load a url.
Problem is that After opening webView Memory leak is happened.
I mean I am not able to remove memory leak.
Here below is my code :-
import UIKit
import Toast_Swift
class WebViewController: UIViewController,UIWebViewDelegate {
//WebView
#IBOutlet weak var webView: UIWebView!
//URL
var strUrl : String? = nil
//Tag
var tag : Int! = 0
//ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
//Delegate of web view
webView.delegate = self
//webView.stringByEvaluatingJavaScript(from: "localStorage.clear();")
self.webView.loadRequest(URLRequest(url: URL(string: self.strUrl!)!))
//Loading View
self.view.makeToastActivity(.center)
}
//MARK :- Web view delegate.
func webViewDidFinishLoad(_ webView: UIWebView) {
//ToastManager.shared.tapToDismissEnabled = true
self.view.hideToastActivity()
}
//Button Back Action
#IBAction func btnBack(_ sender: Any) {
if (tag == 1) {
webView.delegate = nil
self.strUrl = ""
webView.removeCache()
let gotoCreateView = self.storyboard?.instantiateViewController(withIdentifier: "CreateAccountView") as! CreateAccountView
self.present(gotoCreateView, animated: true, completion: nil)
} else {
webView.delegate = nil
self.strUrl = ""
webView.removeCache()
let gotoAboutUsView = self.storyboard?.instantiateViewController(withIdentifier: "AboutUsView") as! AboutUsView
self.present(gotoAboutUsView, animated: true, completion: nil)
}
/*if (1 == tag)
{
webView.delegate = nil
webView.removeCache()
let gotoCreateView = self.storyboard?.instantiateViewController(withIdentifier: "CreateAccountView") as! CreateAccountView
self.present(gotoCreateView, animated: true, completion: nil)
}
else
{
webView.delegate = nil
webView.removeCache()
let gotoAboutUsView = self.storyboard?.instantiateViewController(withIdentifier: "AboutUsView") as! AboutUsView
self.present(gotoAboutUsView, animated: true, completion: nil)
}*/
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
webView.delegate = nil
webView.removeCache()
webView.delegate = self
self.webView.reload()
}
}
extension UIWebView
{
func removeCache()
{
URLCache.shared.removeAllCachedResponses()
URLCache.shared.diskCapacity = 0
URLCache.shared.memoryCapacity = 0
if let cookies = HTTPCookieStorage.shared.cookies {
for cookie in cookies {
HTTPCookieStorage.shared.deleteCookie(cookie)
}
}
}
}
What can I do to remove memory leak?
Thanks

Apart from your code, you need to get these points:
The first thing is try to not add the webView in main view, instead of this you can set alpha or hidden property. If you are using hidden property, then on unhiding make their delegate set to nil and try to manage that WebView will not work in background when it is hidden.
If you are showing in new ViewController, then when we are pushing the WebView, set their delegate and reload the request. Now when you are trying to back from that view. Before poping, set their delegate to nil, set nil to all the local variables that are used in it.
For Example:
On ViewDidLoad: you are setting delegate,
Now when you pop, means move to previous screen use these lines of code:
webView.delegate = nil
webView.removeCache()
And,
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
webView.delegate = nil
webView.removeCache()
webView.delegate = self
self.webView.reload()
}
}
And on Back button:
#IBAction func btnBack(_ sender: Any) {
if (tag == 1) {
webView.delegate = nil
self.strUrl = ""
webView.removeCache()
let gotoCreateView = self.storyboard?.instantiateViewController(withIdentifier: "CreateAccountView") as! CreateAccountView
self.present(gotoCreateView, animated: true, completion: nil)
} else {
webView.delegate = nil
self.strUrl = ""
webView.removeCache()
let gotoAboutUsView = self.storyboard?.instantiateViewController(withIdentifier: "AboutUsView") as! AboutUsView
self.present(gotoAboutUsView, animated: true, completion: nil)
}
}

Related

ViewController loads but does not show

I'm using the Spotify iOS SDK. When a user logs into Spotify using the app, on call back loginVC transitions to musicPlayerVC. But, when a user logs into the app using a web view, once the web view dismisses and the loginVC is shown, the musicPlayerVC is loaded (print statements from viewDidLoad occur), but loginVC does not dismiss and musicPlayerVC does not show.
loginVC:
class loginVC: UIViewController, SPTStoreControllerDelegate, WebViewControllerDelegate {
#IBOutlet weak var statusLabel: UILabel!
var authViewController: UIViewController?
var firstLoad: Bool!
var Information: [String:String]?
override func viewDidLoad() {
super.viewDidLoad()
// NotificationCenter.default.addObserver(self, selector: #selector(self.sessionUpdatedNotification), name: NSNotification.Name(rawValue: "sessionUpdated"), object: nil)
self.statusLabel.text = ""
self.firstLoad = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(self.sessionUpdatedNotification), name: NSNotification.Name(rawValue: "sessionUpdated"), object: nil)
let auth = SPTAuth.defaultInstance()
// Uncomment to turn off native/SSO/flip-flop login flow
//auth.allowNativeLogin = NO;
// Check if we have a token at all
if auth!.session == nil {
self.statusLabel.text = ""
return
}
// Check if it's still valid
if auth!.session.isValid() && self.firstLoad {
// It's still valid, show the player.
print("View did load, still valid, showing player")
self.showPlayer()
return
}
// Oh noes, the token has expired, if we have a token refresh service set up, we'll call tat one.
self.statusLabel.text = "Token expired."
if auth!.hasTokenRefreshService {
self.renewTokenAndShowPlayer()
return
}
// Else, just show login dialog
}
override var prefersStatusBarHidden: Bool {
return true
}
func getAuthViewController(withURL url: URL) -> UIViewController {
let webView = WebViewController(url: url)
webView.delegate = self
return UINavigationController(rootViewController: webView)
}
func sessionUpdatedNotification(_ notification: Notification) {
self.statusLabel.text = ""
let auth = SPTAuth.defaultInstance()
self.presentedViewController?.dismiss(animated: true, completion: { _ in })
if auth!.session != nil && auth!.session.isValid() {
self.statusLabel.text = ""
print("Session updated, showing player")
self.showPlayer()
}
else {
self.statusLabel.text = "Login failed."
print("*** Failed to log in")
}
}
func showPlayer() {
self.firstLoad = false
self.statusLabel.text = "Logged in."
self.Information?["SpotifyUsername"] = SPTAuth.defaultInstance().session.canonicalUsername
OperationQueue.main.addOperation {
[weak self] in
self?.performSegue(withIdentifier: "ShowPlayer", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowPlayer" {
if let destination = segue.destination as? PlayController {
destination.Information = self.Information
}
}
}
internal func productViewControllerDidFinish(_ viewController: SPTStoreViewController) {
self.statusLabel.text = "App Store Dismissed."
viewController.dismiss(animated: true, completion: { _ in })
}
func openLoginPage() {
self.statusLabel.text = "Logging in..."
let auth = SPTAuth.defaultInstance()
if SPTAuth.supportsApplicationAuthentication() {
self.open(url: auth!.spotifyAppAuthenticationURL())
} else {
self.authViewController = self.getAuthViewController(withURL: SPTAuth.defaultInstance().spotifyWebAuthenticationURL())
self.definesPresentationContext = true
self.present(self.authViewController!, animated: true, completion: { _ in })
}
}
func open(url: URL) {
if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:],
completionHandler: {
(success) in
print("Open \(url): \(success)")
})
} else {
let success = UIApplication.shared.openURL(url)
print("Open \(url): \(success)")
}
}
func renewTokenAndShowPlayer() {
self.statusLabel.text = "Refreshing token..."
SPTAuth.defaultInstance().renewSession(SPTAuth.defaultInstance().session) { error, session in
SPTAuth.defaultInstance().session = session
if error != nil {
self.statusLabel.text = "Refreshing token failed."
print("*** Error renewing session: \(error)")
return
}
self.showPlayer()
}
}
func webViewControllerDidFinish(_ controller: WebViewController) {
// User tapped the close button. Treat as auth error
}
}
webController :
import UIKit
import WebKit
#objc protocol WebViewControllerDelegate {
func webViewControllerDidFinish(_ controller: WebViewController)
/*! #abstract Invoked when the initial URL load is complete.
#param success YES if loading completed successfully, NO if loading failed.
#discussion This method is invoked when SFSafariViewController completes the loading of the URL that you pass
to its initializer. It is not invoked for any subsequent page loads in the same SFSafariViewController instance.
*/
#objc optional func webViewController(_ controller: WebViewController, didCompleteInitialLoad didLoadSuccessfully: Bool)
}
class WebViewController: UIViewController, UIWebViewDelegate {
var loadComplete: Bool = false
var initialURL: URL!
var webView: UIWebView!
var delegate: WebViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
print(initialURL)
let initialRequest = URLRequest(url: self.initialURL)
self.webView = UIWebView(frame: self.view.bounds)
self.webView.delegate = self
self.webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(self.webView)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.done))
self.webView.loadRequest(initialRequest)
}
func done() {
self.delegate?.webViewControllerDidFinish(self)
self.presentingViewController?.dismiss(animated: true, completion: { _ in })
}
func webViewDidFinishLoad(_ webView: UIWebView) {
if !self.loadComplete {
delegate?.webViewController?(self, didCompleteInitialLoad: true)
self.loadComplete = true
}
}
func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
if !self.loadComplete {
delegate?.webViewController?(self, didCompleteInitialLoad: true)
self.loadComplete = true
}
}
init(url URL: URL) {
super.init(nibName: nil, bundle: nil)
self.initialURL = URL as URL!
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

Objects of presenting/showing ViewController equal to nil

This func is used for showing/presenting ViewController.
let noteViewController = NoteViewController()
extension NoteTableViewController: UIViewControllerTransitioningDelegate {
func remindLater() {
if let note = notificationNote?.copy() as? Note {
noteViewController.transitioningDelegate = self
noteViewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen
noteViewController.modalTransitionStyle = UIModalTransitionStyle.coverVertical
resultSearchController.searchBar.isHidden = true
present(noteViewController, animated: true, completion: nil)
//show(noteViewController, sender: self)
}
}
}
On the ViewController i am trying to present i've an outlet for UIDatePicker object. On viewdidLoad() method i have this line that causes an error.
frameDatePicker = remindDatePicker.frame
So, i found remindDatePicker and other objects equal to nil.
Where should i find a mistake?

Swift: Thread 1 signal SIGABRT in class AppDelegate: UIResponder, UIApplicationDelegate

Whenever I try to open a different view controller with a show segue it crashes with this error Swift: Thread 1 signal SIGABRT in class AppDelegate: UIResponder, UIApplicationDelegate, I don't know why.
Here's the code of the View Controller that I try to open:
import UIKit
import CoreData
class AddEditVC: UIViewController, NSFetchedResultsControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var item : Item? = nil
#IBOutlet weak var itemName: UITextField!
#IBOutlet weak var imageHolder: UIImageView!
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
override func viewDidLoad() {
super.viewDidLoad()
if item != nil {
itemName.text = item?.name
imageHolder.image = UIImage(data: (item?.image)!)
}
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func addImage(sender: AnyObject) {
let pickerController = UIImagePickerController()
pickerController.delegate = self
pickerController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
pickerController.allowsEditing = true
self.presentViewController(pickerController, animated: true, completion: nil)
}
#IBAction func addImageFromCamera(sender: AnyObject) {
let pickerController = UIImagePickerController()
pickerController.delegate = self
pickerController.sourceType = UIImagePickerControllerSourceType.Camera
pickerController.allowsEditing = true
self.presentViewController(pickerController, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
self.dismissViewControllerAnimated(true, completion: nil)
self.imageHolder.image = image
}
#IBAction func saveTapped(sender: AnyObject) {
if item != nil {
editItem()
} else {
createNewItem()
}
}
func createNewItem() {
let entityDescription = NSEntityDescription.entityForName("Item", inManagedObjectContext: moc)
let item = Item(entity: entityDescription!, insertIntoManagedObjectContext: moc)
item.name = itemName.text
item.image = UIImagePNGRepresentation(imageHolder.image!)
do {
try moc.save()
} catch {
return
}
}
func editItem () {
item?.name = itemName.text
item!.image = UIImagePNGRepresentation(imageHolder.image!)
do {
try moc.save()
} catch {
return
}
}
This error is usually appears when some of #IBOutlet or #IBAction is not assigned in the MainStoryboard.
Please check small circles before them. They should be grayed
It's either what Andriy is saying, or you executed a PerformSegueWithIdentifier(name, nil) with the wrong identifier. Best to add a screenshot of your storyboard & original controller as well to give a definite answer.
Good luck!

Add initial note

I am looking at adding an inital note to the note page within my app. this is so that when people click to the notes part there will be some detail on how to use it rather than just a big empty screen. I have no idea where to implement this though. Could you please help, below is the page where it talks about the dictionaries.
import UIKit
import MessageUI
class DetailViewController: UIViewController, MFMailComposeViewControllerDelegate, UITextViewDelegate {
#IBOutlet weak var tView: UITextView!
#IBAction func BarButton(sender: UIBarButtonItem) {
let textToShare = ""
if let myWebsite = NSURL(string: "")
{
let objectsToShare = [textToShare, myWebsite]
let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
self.presentViewController(activityVC, animated: true, completion: nil)
}
OpenMail()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tView.text = (allNotes[currentNoteIndex] as Note).note
tView.becomeFirstResponder()
// Set controller as swipe gesture recogniser, to allow keyboard dismissal for text box
var swipe: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "dismissKeyboard")
swipe.direction = UISwipeGestureRecognizerDirection.Down
self.view.addGestureRecognizer(swipe)
self.tView.delegate = self
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if tView.text == "" {
allNotes.removeAtIndex(currentNoteIndex)
}
else {
(allNotes[currentNoteIndex] as Note).note = tView.text
}
Note.saveNotes()
noteTable?.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func configuredMailComposeViewController() -> MFMailComposeViewController {
// Open mail controller on screen and prepare with preset values.
let mailComposerVC = MFMailComposeViewController()
var MessageText: String!
MessageText = tView.text
mailComposerVC.mailComposeDelegate = self
mailComposerVC.setToRecipients([""])
mailComposerVC.setSubject("")
mailComposerVC.setMessageBody(MessageText, isHTML: false)
return mailComposerVC
}
func showSendMailErrorAlert() {
// Alert user to email error
let sendMailErrorAlert = UIAlertView(title: "Could Not Send Email", message: "Your device could not send e-mail. Please check e-mail configuration and try again.", delegate: self, cancelButtonTitle: "OK")
sendMailErrorAlert.show()
}
// MARK: MFMailComposeViewControllerDelegate Method
func mailComposeController(controller: MFMailComposeViewController!, didFinishWithResult result: MFMailComposeResult, error: NSError!) {
controller.dismissViewControllerAnimated(true, completion: nil)
}
func OpenMail() {
//Function to open mail composer on screen
let mailComposeViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.presentViewController(mailComposeViewController, animated: true, completion: nil)
} else {
self.showSendMailErrorAlert()
}
}
func dismissKeyboard() {
// Dismiss keyboard for textfield
self.tView.resignFirstResponder()
}
}
note.swift
import UIKit
var allNotes:[Note] = []
var currentNoteIndex:NSInteger = -1
var noteTable:UITableView?
let KAllNotes:String = "notes"
class Note: NSObject {
var date:String
var note:String
override init() {
date = NSDate().description
note = ""
}
func dictionary() -> NSDictionary {
return ["note":note, "date":date]
}
class func saveNotes() {
var aDictionaries:[NSDictionary] = []
for (var i:NSInteger = 0; i < allNotes.count; i++) {
aDictionaries.append(allNotes[i].dictionary())
}
NSUserDefaults.standardUserDefaults().setObject(aDictionaries, forKey: KAllNotes)
// aDictionaries.writeToFile(filePath(), atomically: true)
}
class func loadnotes() {
allNotes.removeAll(keepCapacity: true)
var defaults:NSUserDefaults = NSUserDefaults.standardUserDefaults()
var savedData:[NSDictionary]? = defaults.objectForKey(KAllNotes) as? [NSDictionary]
// var savedData:NSArray? = NSArray(contentsOfFile: filePath())
if let data:[NSDictionary] = savedData {
for (var i:NSInteger = 0; i < data.count; i++) {
var n:Note = Note()
n.setValuesForKeysWithDictionary(data[i] as [NSObject : AnyObject])
allNotes.append(n)
}
}
}
class func filePath() -> String {
var d:[String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.AllDomainsMask, true) as? [String]
if let directories:[String] = d {
var docsDirectory:String = directories[0]
var path:String = docsDirectory.stringByAppendingPathComponent("\(KAllNotes).notes")
return path;
}
return ""
}
}
Thanks in advance
Sam
Add an NSUserDefault boolean that stores whether or not the initial note should be shown, e.g. that the app has been launched for the first time. Then load an initial note accordingly. When a note is added or the initial note is deleted, then change the boolean accordingly so the initial note doesn't show up next time.
You could also initialize your database with an initial note. Not clear from your code how the notes are saved, but this approach would probably rely on the NSUserDefault approach above, except it could be done in the AppDelegate or something.
example:
let InitialSetupComplete = "InitialSetupComplete" // Note: I would define this at the top of a file
let defaults = NSUserDefaults.standardUserDefaults()
if defaults.boolForKey(InitialSetupComplete) {
// Show initial note
}
// Later on when the note is deleted, or modified (or immediately after initial note loaded into the database, see below)
defaults.setBool(true, forKey: InitialSetupComplete)
Would be easier/cleaner just to initialize your database with the initial note in the app delegate (e.g. call within applicationDidFinishLaunching), so your view controller doesn't have to figure this out. Similar code, except you would use setBool right away after the initial note has been saved to the database. I don't know anything about your database from the question, so can't really provide a more detailed example than this. Hope this helps.

Show Game Centre Leaderboard not working in swift

I am coding in swift and I am getting the following error message from my submit button. The leaderboard does not show up in the simulator, just gives me the error message below
<GKGameCenterViewController: 0x7a8d0800> on <Chinese_Quiz.OpeningViewController: 0x78688aa0> whose view is not in the window hierarchy!
Below is all the game centre code from my app.
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
Randomize()
Hide()
authenticateLocalPlayer()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func Submit(sender: AnyObject) {
saveHighscore(Score)
showLeaderboard()
}
func authenticateLocalPlayer(){
var localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(viewController, error) -> Void in
if (viewController != nil) {
self.presentViewController(viewController, animated: true, completion: nil)
}
else {
println((GKLocalPlayer.localPlayer().authenticated))
}
}
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController!)
{
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
func showLeaderboard() {
var vc = self.view?.window?.rootViewController
var gc = GKGameCenterViewController()
gc.gameCenterDelegate = self
vc?.presentViewController(gc, animated: true, completion: nil)
}
func saveHighscore(score:Int) {
//check if user is signed in
if GKLocalPlayer.localPlayer().authenticated {
var scoreReporter = GKScore(leaderboardIdentifier: "ChineseWeather") //leaderboard id here
scoreReporter.value = Int64(Score) //score variable here (same as above)
var scoreArray: [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: {(error : NSError!) -> Void in
if error != nil {
println("error")
}
})
}
}
Solved by turning this:
func showLeaderboard() {
var vc = self.view?.window?.rootViewController
var gc = GKGameCenterViewController()
gc.gameCenterDelegate = self
vc?.presentViewController(gc, animated: true, completion: nil)
}
Into this:
func showLeaderboard() {
var vc = self
var gc = GKGameCenterViewController()
var gameCenterDelegate: GKGameCenterControllerDelegate!
// Above text was replaced from gc.gameCenterDelegate = self
vc.presentViewController(gc, animated: true, completion: nil)
}
I don't know why, but it worked.

Resources