I got UINavigationController inside my app with a rootVC "VC1", VC1 containts a collectionview with 2images inside every cell. When user select cell, navigationcontroller will pass images from cell to new vc "VC2" and then push it to the top of navigationcontroller. My problem is when I take down VC2 via popviewcontroller, VC2 is deallocated correctly but memory stays at the same higher level (after pushing new vc it increases from 60mb to 130mb). I've trying set image to nil, and imageview also but none of this work. Here's some of my code:
class VC1: UIViewController {
var selectedUserPollDetails : VC2?
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItem(at: indexPath) as! AppUserCell
selectedUserPollDetails = VC2()
selectedUserPollDetails?.leftPhoto = cell.leftImageNode.image
selectedUserPollDetails?.rightPhoto = cell.rightImageNode.image
navigationController?.pushViewController(selectedUserPollDetails !, animated: true)
}
}
class VC2: UIViewController {
lazy var arrow : ArrowBack = {
let arrow = ArrowBack()
return arrow
}()
weak var leftPhoto: UIImage?
weak var rightPhoto: UIImage?
var leftPhotoImageview: UIImageView = {
let imageview = UIImageView()
imageview.contentMode = .scaleAspectFill
imageview.layer.cornerRadius = 5
imageview.layer.masksToBounds = true
return imageview
}()
var rightPhotoImageview: UIImageView = {
let imageview = UIImageView()
imageview.contentMode = .scaleAspectFit
imageview.layer.cornerRadius = 5
imageview.clipsToBounds = true
return imageview
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(leftPhotoImageview)
view.addSubview(rightPhotoImageview)
view.addSubview(arrow)
arrow.addTarget(self, action: #selector(handleArrowBack), for: .touchUpInside)
}
func handleArrowBack(){
navigationController?.popViewController(animated: true)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
leftPhotoImageview.frame = CGRect(x: 100, y: 0, width: 100, height: 100)
rightPhotoImageview.frame = CGRect(x: 100, y: 200, width: 100, height: 100)
if leftPhoto != nil, rightPhoto != nil{
leftPhotoImageview.image = leftPhoto
rightPhotoImageview.image = rightPhoto
}
}
deinit{
leftPhoto = nil
rightPhoto = nil
leftPhotoImageview.image = nil
rightPhotoImageview.image = nil
}
I've even added deinit at the end to make sure photos are deallocated. So basically when i'm trying to push VC2 again (after pop) amount of memory is doubled again (260mb) and so on... what causing this problem? what am i doing wrong?
btw. i've omitted less important func and vars
You defo have a memory leak. I believe everytime
you push a new view via the nav controller it creates a brand new view i.e. the view is completely new and isn't reused. If you have strong references within the view you've pushed to they won't be released unless you deinit as they have a strong ref to view you've pushed so they linger around. You mentioned you deinit those items. Have you also tried marking leftPhotoImageView and rightPhotoImageView also as weak properties? Something is holding onto those images it would seem.
You could also change deinit to leftPhotoImageview = nil and rightPhotoImageView = nil rather than setting the imageview.image property to nil if that makes sense.
Ok, i think i found a solution, i don't know why i haven't trying this one, so i marked my imageview as lazy, and now memory is decreasing after popping vc
Related
basically my current setup is like this
one storyboard ViewController with 3 types of UI View(container, front view, back view) inside of it.
what i want to accomplish (and i don't know how to implement #2)
user enters the data on the form(front of the card- View Controller number 1)
clicks the save button (do animation flipping and redirect to a new view controller)
the new view controller loads up (back of the card - View Controller number 2)
this is the current code flip example:
import UIKit
class HomeViewController: UIViewController {
#IBOutlet weak var goButton: UIButton!
#IBOutlet weak var optionsSegment: UISegmentedControl!
let owlImageView = UIImageView(image: UIImage(named:"img-owl"))
let catImageView = UIImageView(image: UIImage(named:"img-cat"))
var isReverseNeeded = false
override func viewDidLoad() {
super.viewDidLoad()
title = "Transitions Test"
setupView()
}
fileprivate func setupView() {
let screen = UIScreen.main.bounds
goButton.layer.cornerRadius = 22
//container to hold the two UI views
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 250))
containerView.backgroundColor = UIColor(red: 6/255, green: 111/255, blue: 165/255, alpha: 1.0)
containerView.layer.borderColor = UIColor.white.cgColor
containerView.layer.borderWidth = 2
containerView.layer.cornerRadius = 20
containerView.center = CGPoint(x: screen.midX, y: screen.midY)
view.addSubview(containerView)
//front view
catImageView.frame.size = CGSize(width: 100, height: 100)
catImageView.center = CGPoint(x: containerView.frame.width/2, y: containerView.frame.height/2)
catImageView.layer.cornerRadius = 50
catImageView.clipsToBounds = true
//back view
owlImageView.frame.size = CGSize(width: 100, height: 100)
owlImageView.center = CGPoint(x: containerView.frame.width/2, y: containerView.frame.height/2)
owlImageView.layer.cornerRadius = 50
owlImageView.clipsToBounds = true
containerView.addSubview(owlImageView)
}
#IBAction func goButtonClickHandler(_ sender: Any) {
doTransition()
}
fileprivate func doTransition() {
let duration = 0.5
var option:UIViewAnimationOptions = .transitionCrossDissolve
switch optionsSegment.selectedSegmentIndex {
case 0: option = .transitionFlipFromLeft
case 1: option = .transitionFlipFromRight
case 2: option = .transitionCurlUp
case 3: option = .transitionCurlDown
case 4: option = .transitionCrossDissolve
case 5: option = .transitionFlipFromTop
case 6: option = .transitionFlipFromBottom
default:break
}
if isReverseNeeded {
UIView.transition(from: catImageView, to: owlImageView, duration: duration, options: option, completion: nil)
} else {
UIView.transition(from: owlImageView, to: catImageView, duration: duration, options: option, completion: nil)
}
isReverseNeeded = !isReverseNeeded
}
}
There are a few alternatives for transition between view controllers with a flipping animation:
You can define a segue in IB, configure that segue to do a horizontal flipping animation:
If you want to invoke that segue programmatically, give the segue a “Identifier” string in the attributes inspector and then you can perform it like so:
performSegue(withIdentifier: "SecondViewController", sender: self)
Alternatively, give the actual destination view controller’s scene a storyboard identifier, and the presenting view controller can just present the second view controller:
guard let vc = storyboard?.instantiateViewController(identifier: "SecondViewController") else { return }
vc.modalTransitionStyle = .flipHorizontal
vc.modalPresentationStyle = .currentContext
show(vc, sender: self)
If this standard flipping animation isn’t quite what you want, you can customize it to your heart’s content. iOS gives us rich control over custom transitions between view controller by specifying transitioning delegate, supplying an animation controller, etc. It’s a little complicated, but it’s outlined in WWDC 2017 Advances in UIKit Animations and Transitions: Custom View Controller Transitions (about 23:06 into the video) and WWDC 2013 Custom Transitions Using View Controllers.
How to remove subviews?
I am trying to integrate GIF by creating UIView and UIImageView programmatically.
It works fine to show GIF but when the function of hiding if is called, there is no response.
Here are the codes of both functions.
class CustomLoader: UIView {
static let instance = CustomLoader()
var viewColor: UIColor = .black
var setAlpha: CGFloat = 0.5
var gifName: String = ""
lazy var transparentView: UIView = {
let transparentView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
transparentView.backgroundColor = viewColor.withAlphaComponent(setAlpha)
transparentView.isUserInteractionEnabled = false
return transparentView
}()
lazy var gifImage: UIImageView = {
var gifImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
gifImage.contentMode = .scaleAspectFit
gifImage.center = transparentView.center
gifImage.isUserInteractionEnabled = false
gifImage.loadGif(name: gifName)
return gifImage
}()
func showLoaderView() {
self.addSubview(self.transparentView)
self.transparentView.addSubview(self.gifImage)
self.transparentView.bringSubview(toFront: self.gifImage)
UIApplication.shared.keyWindow?.addSubview(transparentView)
}
func hideLoaderView() {
self.transparentView.removeFromSuperview()
}
}
A couple of thoughts:
I’d suggest you add a breakpoint or a logging statement in hideLoaderView and make sure you’re getting to that line.
You should make the init method to this class private to make sure you’re not calling hideLoaderView on some separate instance. When dealing with singletons, you want to make sure you can’t accidentally create another instance.
But I tested your code, and it works fine. Your problem probably rests with where and how you call this (and making init private, you might find where you might be using it inappropriately).
In the comments below, you said:
I simply call the function "CustomLoader().hideLoaderView()" Both are being called technically. What do you mean by "where I using it inappropriately?"
That is the root of the problem.
The CustomLoader() of CustomLoader().hideLoaderView() will create a new instance of CustomLoader with its own transparencyView, etc., which is precisely what the problem is. You’re not hiding the old view that was presented earlier, but trying to hide another one that you just created and was never displayed.
If you instead use that static, e.g. CustomLoader.instance.showLoaderView() and CustomLoader.instance.hideLoaderView(), then the problem will go away. Then you will be hiding the same view that your previously showed.
By the way, a few other unrelated observations:
If this is a singleton or shared instance, the convention would be to call that static property shared, not instance.
By the way, you aren’t using this CustomLoader as a UIView, so I’d not make it a UIView subclass. Don’t make it a subclass of anything.
You would obviously eliminate that self.addSubview(transparentView) line, too.
The bringSubview(toFront:) call is unnecessary.
You should avoid referencing UIScreen.main.bounds. You don’t know if your app might be in multitasking mode (maybe this isn’t an issue right now, but it’s the sort of unnecessary assumption that will cause problems at some later date). Just refer to the bounds of the UIWindow to which you’re adding this. You should also update this frame when you show this view, not when you create it (in case you changed orientation in the intervening time, or whatever).
By the way, using keyWindow is discouraged in iOS 13 and later, so you might eventually want to remove that, too.
When adding the gifImage (which I’d suggest renaming to gifImageView because it’s an image view, not an image), you should not reference the center of its superview. That’s the coordinate of the transparent view in its super view’s coordinate system, which could be completely different than the transparent view’s own coordinate system. In this case, it just happens to work, but it suggests a fundamental misunderstanding of view coordinate systems. Reference the bounds of the transparentView, not its center.
If you’re going to expose viewColor and setAlpha, you should pull the setting of the transparentView’s color out of the lazy initializer and into showLoaderView, at the very least. Right now, if you show the loader once, and then change the color, and try to show it again, you won’t see the new color.
The same issue applies with the gif image. So, I’d move that to the didSet observer.
Thus, pulling this all together:
class CustomLoader{
static let shared = CustomLoader()
private init() { }
var dimmingColor: UIColor = .black
var dimmingAlpha: CGFloat = 0.5
var gifName: String = "" { didSet { gifImage.loadGif(name: gifName) } }
lazy var transparentView: UIView = {
let transparentView = UIView()
transparentView.isUserInteractionEnabled = false
return transparentView
}()
lazy var gifImageView: UIImageView = {
var gifImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
gifImage.contentMode = .scaleAspectFit
gifImage.isUserInteractionEnabled = false
return gifImage
}()
func showLoaderView() {
guard let window = UIApplication.shared.keyWindow else { return }
transparentView.frame = window.bounds
transparentView.backgroundColor = dimmingColor.withAlphaComponent(dimmingAlpha)
gifImageView.center = CGPoint(x: transparentView.bounds.midX, y: transparentView.bounds.midY)
transparentView.addSubview(gifImageView)
window.addSubview(transparentView)
}
func hideLoaderView() {
transparentView.removeFromSuperview()
}
}
Why you are using transparentView while you are have a CustomLoader instance view
Try to use this
class CustomLoader: UIView {
static let instance = CustomLoader()
var viewColor: UIColor = .black
var setAlpha: CGFloat = 0.5
var gifName: String = ""
init() {
super.init(frame: UIScreen.main.bounds)
backgroundColor = viewColor.withAlphaComponent(setAlpha)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var gifImage: UIImageView = {
var gifImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
gifImage.backgroundColor = .red
gifImage.contentMode = .scaleAspectFit
gifImage.center = center
gifImage.isUserInteractionEnabled = false
gifImage.loadGif(name: gifName)
return gifImage
}()
func showLoaderView() {
addSubview(self.gifImage)
UIApplication.shared.keyWindow?.addSubview(self)
}
func hideLoaderView() {
removeFromSuperview()
}
}
I add an image to my view by the following code if the count is zero and remove it otherwise:
var coverImageView = UIImageView()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if count == 0 {
let coverImage = UIImage(named: "AddFirstRecord")!
coverImageView = UIImageView(image: coverImage)
coverImageView.frame = CGRect(x: 20, y: 5, width: tableView.frame.width-20, height: 100)
view.addSubview(coverImageView)
} else {
DispatchQueue.main.async {
self.coverImageView.removeFromSuperview()
}
}
}
The problem is that it adds the image to the view, but removeFromSuperview does not work. (I made sure that it reaches to the else condition by debugging). I did the process in the main queue as well to be sure that the problem does not relate to threads. I wonder where is the origin of the issue?
In viewWillAppear the view still is not prepared completely to view. So removingFromSuperview does not have any effects. Instead, we should do the action inside viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if fetchedResultsController.fetchedObjects?.count == 0 {
let coverImage = UIImage(named: "AddFirstRecord")!
coverImageView.image = coverImage
coverImageView.frame = CGRect(x: 20, y: 5, width: tableView.frame.width-20, height: 100)
view.addSubview(coverImageView)
} else {
coverImageView.removeFromSuperview()
}
}
From Apple Documentation :
viewDidLayoutSubviews()
Called to notify the view controller that its
view has just laid out its subviews.
Your view controller can override this method to make changes after
the view lays out its subviews. The default implementation of this
method does nothing.
I am trying to Flip two UIViews. I've try to flip UIView using programmatically and it works perfect. But when i've try to flip UIView that i created in storyboard it not works, First time it flip UIView but second time it flip blank UiViews? Any one have any idea is there any mistake in my code?
In this picture Top left Debug view Hierarchy picture is before animating button and bottom left Debug view Hierarchy picture is after animating picture.
When second time i animate the UIView it Flip like this below picture.
class ViewController: UIViewController {
#IBOutlet var container: UIView!
#IBOutlet var blueSquare : UIView!
#IBOutlet var redSquare : UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func animateButtonTapped(sender: AnyObject) {
// create a 'tuple' (a pair or more of objects assigned to a single variable)
var views : (frontView: UIView, backView: UIView)
if ((self.redSquare.superview) != nil) {
views = (frontView: self.redSquare, backView: self.blueSquare)
}
else {
views = (frontView: self.blueSquare, backView: self.redSquare)
}
// set a transition style
let transitionOptions = UIViewAnimationOptions.TransitionFlipFromLeft
// with no animation block, and a completion block set to 'nil' this makes a single line of code
UIView.transitionFromView(views.frontView, toView: views.backView, duration: 1.0, options: transitionOptions, completion: nil)
}
}
Programmatically
That code is perfectly works.
let container = UIView()
let redSquare = UIView()
let blueSquare = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// set container frame and add to the screen
self.container.frame = CGRect(x: 60, y: 60, width: 200, height: 200)
self.view.addSubview(container)
// set red square frame up
// we want the blue square to have the same position as redSquare
// so lets just reuse blueSquare.frame
self.redSquare.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
self.blueSquare.frame = redSquare.frame
// set background colors
self.redSquare.backgroundColor = UIColor.redColor()
self.blueSquare.backgroundColor = UIColor.blueColor()
// for now just add the redSquare
// we'll add blueSquare as part of the transition animation
self.container.addSubview(self.redSquare)
}
#IBAction func animateButtonTapped(sender: AnyObject) {
// create a 'tuple' (a pair or more of objects assigned to a single variable)
var views : (frontView: UIView, backView: UIView)
if((self.redSquare.superview) != nil){
views = (frontView: self.redSquare, backView: self.blueSquare)
}
else {
views = (frontView: self.blueSquare, backView: self.redSquare)
}
// set a transition style
let transitionOptions = UIViewAnimationOptions.TransitionFlipFromLeft
// with no animation block, and a completion block set to 'nil' this makes a single line of code
UIView.transitionFromView(views.frontView, toView: views.backView, duration: 1.0, options: transitionOptions, completion: nil)
}
UPDATE
var check = true
#IBAction func animateButtonTapped(sender: AnyObject) {
// create a 'tuple' (a pair or more of objects assigned to a single variable)
var views : (frontView: UIView, backView: UIView)
if (check == true) {
views = (frontView: self.redSquare, backView: self.blueSquare)
check = false
}
else {
views = (frontView: self.blueSquare, backView: self.redSquare)
check = true
}
// set a transition style
let transitionOptions : UIViewAnimationOptions = [UIViewAnimationOptions.TransitionFlipFromLeft, UIViewAnimationOptions.ShowHideTransitionViews]
// with no animation block, and a completion block set to 'nil' this makes a single line of code
UIView.transitionFromView(views.frontView, toView: views.backView, duration: 1.0, options: transitionOptions, completion: nil)
}
The default behaviour for transitionFromView removes the view after animation.
let transitionOptions: UIViewAnimationOptions = [.TransitionFlipFromLeft, .ShowHideTransitionViews]
From the documentation: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/swift/struct/c:#E#UIViewAnimationOptions
ShowHideTransitionViews
When present, this key causes views to be hidden or shown (instead of removed or added) when performing a view transition. Both views must already be present in the parent view’s hierarchy when using this key. If this key is not present, the to-view in a transition is added to, and the from-view is removed from, the parent view’s list of subviews.
Ah in storyboard I see that you add both views to the container but in code you only add the redsquare. Perhaps remove the bluesquare from being within the container in storyboard?
I'm developping application in Swift.
This application has many view and I would like to put a UIProgressView on all views
Can we get an array of all storyboard views ?
for exemple :
self.progressBar = UIProgressView(progressViewStyle: .Bar)
self.progressBar?.center = view.center
self.progressBar?.frame = CGRect(x: 0, y: 20, width: view.frame.width, height: CGFloat(1))
self.progressBar?.progress = 1/2
self.progressBar?.trackTintColor = UIColor.lightGrayColor();
self.progressBar?.tintColor = UIColor.redColor();
var arrayViewController : [UIViewController] = [...,...,...]
for controller in arrayViewController {
controller.view.addSubview(self.progressBar)
}
Thank you
Ysée
I assume that what you really want is to have the progress displayed on every view IF there is an operation in progress.
There are many ways to do that (using delegation, NSNotificationCenter, …) but the easiest I can think of would be to rely on viewWillAppear
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Check if there's an operation in progress and add progressView if relevant
}
For the user, it will effectively look like you added the progress view to all views.
Why not create a base class that has a lazy stored property of type UIProgressView ? Optionally you can have two methods setProgressViewHidden(hidden : Bool) in order to easily show and hide the progress view and setProgress(progress : Float) to update the progress. Then all your view controllers can subclass this base class and conveniently interact with the progress view.
class ProgressViewController : UIViewController {
lazy var progressView : UIProgressView = {
[unowned self] in
var view = UIProgressView(frame: CGRectMake(0, 20, self.view.frame.size.width, 3))
view.progress = 0.5
view.trackTintColor = UIColor.lightGrayColor()
view.tintColor = UIColor.redColor()
self.view.addSubview(view)
return view
}()
}
To read more about lazy stored properties, check: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html