memory leak when using images in Swift? - ios

I have a very simple app with two viewControllers at the moment. I have a transition between them (with a segue of course) and both screens have a lot of images and buttons with images.
What I discovered was that there is a memory leak when you alternate between the screens. I am guessing, that somehow the images are loaded again each time you open the viewController again. Around 6MB is added each time and I am pretty sure that the images are less than 1MB in total. So maybe there is something else craving all the memory?
I have 6 image Views and I use the cross dissolve transition in the default modus. I wish I could copy some code here, but it is a big project and I would not know what code would be helpful.
So my question would be: what is causing this problem (and preferable how can I fix this)? Is it the images, or maybe the segues with the transition.
Any help would be really appreciated! Thanks!
--- Edit: Here's some bits of code with the images
// variables of the bottom menu bar
#IBOutlet weak var cameraButton: UIButton!
#IBOutlet weak var redoButton: UIButton!
#IBOutlet weak var saveButton: UIButton!
#IBOutlet weak var shareButton: UIButton!
#IBOutlet weak var overLay: UIImageView!
// this function lets the statusbar disappear
override func prefersStatusBarHidden() -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
overLay.image = nil
// variables for accessing web images
let url = NSURL(string: "http://www.fteunen.com/app/overLay.png")
let urlRequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
if error != nil {
println("Something happened i guess, not good!!")
} else {
//let image = UIImage(data: data)
self.overLay.image = UIImage(data: data)
}
})
}
And I don't know where I can find code from the segues. I just clicked on some options for that.
---- EDIT: The second viewController (the one with all the images) and the dismissViewController:
class ViewController2: UIViewController {
// variables of the bottom menu bar
#IBOutlet weak var cameraButton: UIButton!
#IBOutlet weak var redoButton: UIButton!
#IBOutlet weak var saveButton: UIButton!
#IBOutlet weak var shareButton: UIButton!
#IBOutlet weak var overLay: UIImageView!
// this function lets the statusbar disappear
override func prefersStatusBarHidden() -> Bool {
return true
}
override func viewDidLoad() {
super.viewDidLoad()
overLay.image = nil
// variables for accessing web images
let url = NSURL(string: "http://www.fteunen.com/app/overLay.png")
let urlRequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
if error != nil {
println("Something happened i guess, not good!!")
} else {
//let image = UIImage(data: data)
self.overLay.image = UIImage(data: data)
}
})
}
override func viewDidLayoutSubviews() {
//code
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func didTakePhoto(sender: AnyObject) {
//code
})
}
override func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?) {
dismissViewControllerAnimated(true) {
// Optional completion handler code here
}
}
#IBAction func redoPicture(sender: AnyObject) {
// code
})
}

You are leaking memory because you are continuously creating new view controllers which are all essentially piling on top of each other and downloading images. You should perform a segue and then dismiss that view controller to return to the previous view controller. In your second view controller where you are trying to segue back to the first controller call dismissViewControllerAnimated in either of the following ways:
dismissViewControllerAnimated(true, completion: nil)
or:
dismissViewControllerAnimated(true) {
// Optional completion handler code here
}

Memory Leak due to constantly creating new VC ontop of VC
Use Unwind.
In MainViewController
#IBAction unwindSegue(segue: UIStoryboardSegue, sender: UIStoryboardSegue){//any code you want executed upon return to mainVC}
In NewViewController
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){//pass any information from New to Main here}
Then, in NewVC, simply control+drag from whichever UIButton you want to cause the unwind to NewVC's Exit symbol and select unwindSegue
*
*
NOTE: Also if you want the unwind to happen programmatically instead of from a Button. Control+drag from NewVC (yellow button) to exit(red-orange button)
, this will create an unwind segue under "Exit". Select this "Unwind segue" and in attributes inspector give it an identifier.
Now in NewVC create a function
func NameYourFunc(){performSegueWithIdentifier("TheIdentiferYouUsed", sender: self)}
and anywhere in your NewVC code when you want to perform this unwind simply call NameYourFunc()

Related

Tyring to set UIImage from different fiew via delegation

I have a view controller with a UIImage that will be sent to a service. I'm trying to load this image from another view so I'm using delegation. However I cant seem to understand why it will not work. I set some messages to print in the console and I see them right before the call to the method but its not setting the variables and I'm not sure why.
The view are linked via a navigation controller and to return I use popViewController and to go to the other view controller I use a simple show segue from the button. The "confirmation" of the selected image is done with a button that is supposed to send the image to the previous view controller.
This is the main controller with the UIImage I want to set from another view:
import UIKit
class MainNewPost: UIViewController{
override var preferredStatusBarStyle: UIStatusBarStyle{
return.lightContent
}
var imageForUpload: UIImage?
var isImageSelected: Bool = false
#IBOutlet weak var newPostBox: TVUIView!
#IBOutlet weak var newPost: UIButton!
#IBAction func sendNewPost(_ sender: Any) {
var imageId = ""
if isImageSelected {
imageId = UUID().uuidString
ImageWS.sendIamge(imageId: imageId, image: imageForUpload!, {()}, error: {(errorMessate) in
print(errorMessate)
})
}
if (newPostBox.text!.isEmpty) {
Util.showMessage(controller: self, message: "Ingrese un texto", seconds: 5)
} else {
PostWS.newPost({() in
Util.showMessage(controller: self, message: "Enviaste un mensaje!", seconds: 3.0)
}, img: imageId, postBody: newPostBox.text!, personId: PersonBE.shared!.personId, posterName: PersonBE.shared!.displayName, error: {(errorMessage) in print(errorMessage)})
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension MainNewPost: NewPostDelegate {
// This is not executing. I'm calling it since I see the console message by the flag and image dont get set
func selectedImage(_ image: UIImage, _ isImageSelected: Bool) {
self.imageForUpload = image
self.isImageSelected = isImageSelected
}
}
This is the view controller where I select the image:
import UIKit
protocol NewPostDelegate {
func selectedImage(_ image: UIImage, _ isImageSelected: Bool)
}
class imageSelectViewController: UIViewController {
var postDelegate: NewPostDelegate?
#IBOutlet weak var imageView: UIImageView!
var imagePicker: ImagePicker!
override func viewDidLoad() {
super.viewDidLoad()
self.imagePicker = ImagePicker(presentationController: self, delegate: self)
}
#IBAction func showImagePicker(_ sender: UIButton) {
self.imagePicker.present(from: sender)
}
override var preferredStatusBarStyle: UIStatusBarStyle{
return.lightContent
}
#IBAction func clickBtnBack(_ sender: Any){
self.navigationController?.popViewController(animated: true)
}
#IBAction func selectImage(_ sender: Any) {
// I see this message printed in the console
// This button is suposed to send the selected image to the previous view controller
print("In selectImage button")
self.postDelegate?.selectedImage(imageView.image!, true)
self.navigationController?.popViewController(animated: true)
}
}
extension imageSelectViewController: ImagePickerDelegate {
func didSelect(image: UIImage?) {
// I see this message printed in the console
print("In didSelect delegate method")
self.imageView.image = image
self.postDelegate?.selectedImage(image!, true)
}
}
Thanks!
It seems that var postDelegate: NewPostDelegate? in your imageSelectViewController instance remains nil. Which means that even if you're calling a delegate method, there is no delegate specified itself.
In order to fix it you should assign your MainNewPost instance reference to var postDelegate: NewPostDelegate? of your destination imageSelectViewController while preparing for segue.

UISwitch value inside a nib does not being updated

My switch is inside of a nib which is inside of a tableview header. When I click the switch it only does the pushSwitch.isOn and not !pushSwitch.isOn. It only gives me the values inside of the pushSwitch and doesn't seem to go inside of !pushSwitch.isOn
Thanks
protocol PhoneNotificationHeaderViewDelegate: class {
func copyPreferredSettingsRequested()
func textNotificationSwitchTapped()
func pushNotificationSwitchTapped()
}
class PhoneNotificationHeaderView: UITableViewHeaderFooterView {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var textSwitch: UISwitch!
#IBOutlet weak var pushSwitch: UISwitch!
#IBOutlet weak var copyPreferredSettingsButton: UIButton!
#IBOutlet weak var notificationView: UIView!
weak var delegate: PhoneNotificationHeaderViewDelegate?
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
}
#IBAction func textNotificationSwitchTapped(_ sender: AnyObject) {
self.delegate?.textNotificationSwitchTapped()
}
#IBAction func pushNotificationSwitchTapped(_ sender: UISwitch) {
self.delegate?.pushNotificationSwitchTapped()
}
#IBAction func copyPreferredSettingsButtonTapped() {
self.delegate?.copyPreferredSettingsRequested()
}
override var backgroundColor: UIColor? {
didSet {
print("backgroundColor")
}
}
Here is my switch inside my VC:
extension PhoneNotificationViewController: PhoneNotificationHeaderViewDelegate {
func pushNotificationSwitchTapped() {
guard let phoneNotificationHeader = Bundle(for: type(of: self)).loadNibNamed("PhoneNotificationHeaderView", owner: self, options: nil)?.first as? PhoneNotificationHeaderView else {
return
}
if phoneNotificationHeader.pushSwitch.isOn{
//Disable Firebase from sending
Globals.sharedInstance.pushStatus = true
phoneNotificationHeader.pushSwitch.setOn(false, animated: true)
}else{
Globals.sharedInstance.pushStatus = false
phoneNotificationHeader.pushSwitch.setOn(true, animated: true)
}
self.refreshUI()
}
There is no such thing as a "a nib which is inside of a tableview header". Every time you load a nib by saying loadNibNamed, you get a fresh copy of the view that it contains. So every time you call pushNotificationSwitchTapped you are a getting a whole new switch fresh from the nib, and sticking it in your interface. That is probably not what you intend! And of course the switch comes from the nib in only one state, namely the state that you set it in the nib editor: it is On.
You need to abandon completely this incorrect architecture based on a misapprehension about what a nib is. You should load the nib once, and from then on you should refer to the switch by talking to the switch itself, the one that is now in your interface.

Swift delegate doesn't get called

I'm using PanelKit for an iOS app that I'm developing right now.
I have set up a panel, which is a ViewController, and have set up a button in that ViewController. When pressed, I want a picture to be displayed in another ViewController (the main VC that's instantiated when the app is launched). I'm using a delegate for this. However, my delegate function never gets executed. Here's my code:
Delegate:
protocol IMGPickerDelegate: class {
func didFinishPicking(_ imageData: UIImage)
}
Panel ViewController:
class IMGLibraryViewController: UIViewController, PanelContentDelegate {
weak var delegate: IMGPickerDelegate?
#IBAction func fireButton(_ sender: Any) {
let imageSample = UIImage(named: "sample")!
didPickImage(imageDData: imageSample)
}
func didPickImage(imageDData: UIImage) {
delegate?.didFinishPicking(imageDData)
}
Main ViewController:
class ImageDisplayViewController: UIViewController, IMGPickerDelegate {
#IBOutlet weak var imageDisplayViewOutlet: UIImageView!
var imgLibrary = IMGLibraryViewController()
func didFinishPicking(_ imageData: UIImage) {
imageDisplayViewOutlet.image = imageData
}
override func viewDidLoad() {
super.viewDidLoad()
imgLibrary.delegate = self
}

Swift 3 - UIView does not always display its content

I have a UIViewController (Main Menu) that is displayed after my other UIViewController (Loading Screen) finishes downloading/syncing data from the web. My Main Menu is having problems as it doesn't always display all the UIButtons that are on the view when it shows up! There is an inconsistancy, 1 out of every 10 times the Main Menu loads, all the UIButtons will appear.
Here's how the view should look:
Here's how the view usually shows up:
I can put my finger where the UIButtons should be and move my finger away and they'll appear. I can also tap where they should be and my segues will still trigger, but the UIButtons dont just automatically show up. It looks like
I attempted to add a MainMenuView.setNeedsDisplay() to the ViewDidLoad function and it didn't help. I also created a test UIButton on the view that triggers a MainMenuView.setNeedsDisplay() command but the hidden UIButtons remain hidden.
If I leave the screen idle for 30 or so seconds, all the UIButtons will randomly appear without me having to manually make them appear.
The Main Menu view is normal and all UIButtons are visible if I segue to it from any part of the app aside from my Loading Screen.
Edit: Main Menu Code - (references are Strong)
import UIKit
import CoreData
class MainMenuViewController: UIViewController {
// MARK: References
#IBOutlet var btnGasManifolds: UIButton!
#IBOutlet var btnAirCompressors: UIButton!
#IBOutlet var btnVacuumPumps: UIButton!
#IBOutlet var btnUnitConversion: UIButton!
#IBOutlet var btnCFMCalculator: UIButton!
#IBOutlet var mainMenuView: UIView!
var userData = [NSManagedObject]()
// MARK: Functions
override func viewDidLoad() {
var name = String()
for duserData in userData {
name = duserData.value(forKey: "nameFull") as! String
}
print("Main Menu\n->\(name)")
backgroundSetup()
}
override func viewDidAppear(_ animated: Bool) {
mainMenuView.setNeedsDisplay()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func btnTest(_ sender: Any) {
mainMenuView.setNeedsDisplay()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any!) {
if segue.identifier == "settingsSegue" {
let destination = segue.destination as! SettingsViewController
destination.userData = userData
}
}
// Background Setup
func backgroundSetup() {
let background = UIImage(named: "Data Screens")
var imageView : UIImageView!
imageView = UIImageView(frame: view.bounds)
imageView.contentMode = UIViewContentMode.scaleAspectFill
imageView.clipsToBounds = true
imageView.image = background
imageView.center = view.center
view.addSubview(imageView)
self.view.sendSubview(toBack: imageView)
}
}
The code which creates and shows the buttons after fetching the data from server needs to run on Main UI thread. If the code is running the background thread it will not properly display the UI elements and will take some time or manual scroll before showing the controls.
Run the code on Dispatch main queue.
dispatch_async(dispatch_get_main_queue(),{
//do some work
})
OR
Override one of the methods from ViewWillLayoutSubviews and ViewDidLayoutSubviews accordingly and run your code in these methods.

Show UIView/UIImage/UITextView while function is running

OBJECTIVE
I am trying to display a UIView, UIImage, and UITextView when a function is running in order to let a user know it is processing (similar to an Activity Indicator, but more customized).
PROBLEM
When the code below is processing, the UIView, UIImage, and UITextView don't display until a moment before the function finishes running (instead of showing as soon the function starts running and hiding when the function finishes).
CURRENT APPROACH:
I have created a UIView (loadingView) which contains and image (loadingIcon) and a textView (loadingText) explaining to the user that the app is processing.
I have also created a function called isLoading, which displays or hides all 3, rather than repeating these lines multiple times. I have tested setting isLoading to true and false in the viewDidLoad to make sure that it is working properly.
#IBOutlet weak var loadingView: UIView!
#IBOutlet weak var loadingIcon: UIView!
#IBOutlet weak var loadingText: UIView!
override func viewDidLoad() {
super.viewDidLoad()
isLoading(false)
}
func isLoading(_ loadStatus: Bool) {
if loadStatus == true {
loadingView.isHidden = false
loadingIcon.isHidden = false
loadingText.isHidden = false
} else {
loadingView.isHidden = true
loadingIcon.isHidden = true
loadingText.isHidden = true
}
}
#IBAction func sendButtonPressed(_ sender: AnyObject) {
isLoading(true)
... //process information, which takes some time
isLoading(false)
}
Any help, suggestions, or thoughts are immensely appreciated. Thank you.
You are running the process on the main queue so your UI appears to hang until it is finished. You need to process the information in the background. A common pattern you might use would be:
#IBAction func sendButtonPressed(_ sender: AnyObject) {
isLoading(true)
// Do the processing in the background
DispatchQueue.global(qos: .userInitiated).async {
... //process information, which takes some time
// And update the UI on the main queue
DispatchQueue.main.async {
isLoading(false)
}
}
}

Resources