I'm trying to understand how xcode debugging tool works in terms of detecting retain cycles.
I have a simple Parent and Child view controllers both holds references to each other.
And after executing app opening closing VC several time, when I open debugging tool it neither shows that there is an issue with retain cycle nor runtime issue.
Please find below the code example and attached screenshot of xcode debugging tool
class ViewController: UIViewController {
var child: ChildViewController?
#IBAction func open(_ sender: Any) {
performSegue(withIdentifier: "segueChild", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "segueChild") {
child = segue.destination as? ChildViewController
child?.parentVC = self
}
}
}
class ChildViewController: UIViewController {
var parentVC: ViewController?
#IBAction func close(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
[EDIT, the real answer]
I think you're simply misreading the visual debugger. Taking a closer look at your screen captures, those memory diagrams are actually categorized under Retain Cycles.
Note however:
To actually waste memory, you'll need to abandon all references to the parent UIViewController. As long as the parent remains accessible, both parent and child are accessible from somewhere, even though they have a retain cycle.
(If you replace the child VC with a new one, the previous cycle actually broken and replaced by a new one. By constantly updating the child VC property, you're not wasting anything either.)
Imagine (all arrows are strong):
V--------------------
RootWindow --> GrandParentVC --> ParentVC --> ChildVC --^
This is not a problem.
Now suppose we replace GrandParentVC. An unreachable cycle is created:
RootWindow --> ANewVC
V--------------------
ParentVC --> ChildVC --^
Related
I've been looking through a Coordinator tutorial and it brought up a problem with code I've written in the past.
Namely, when reusing a view controller I've used a property to be able to display different elements depending on which view controller the user arrived from. This is described in the above tutorial as a hack.
For example I segue to labelviewcontroller using
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "label" {
let vc = segue.destination as! LabelViewController
vc.originalVC = self
}
}
and then on labelViewController have a property
var originalVC: ViewController?
which I then change the items in viewDidLoad() through
override func viewDidLoad() {
super.viewDidLoad()
if originalVC != nil {
label.text = "came direct"
imageView.isHidden = true
}
else {
label.text = "button"
imageView.isHidden = false
}
}
I've a working example project here: https://github.com/stevencurtis/ReusibilityIssues
Now, I know the answer might be use the Coordinator tutorial, but is there any other method that I can use to simple reuse a viewController for two different circumstances, rather than using a property or is there anyway to clean this up to be acceptable practice?
You can do that without passing originalVC just by checking parent type if you are pushing it inside a navigation controller like this :
if let p = parent {
if p.isKind(of: OriginalViewController.self){
//it pushed in navigation controller stack after OriginalViewController
}
}
but is there any other method that I can use to simple reuse a viewController for two different circumstances
If the "two different circumstances" you describe are very different (by this I mean "require very different lines of code to be run"), then you should create two different view controller classes, because otherwise you would be violating the Single Responsibility Principle.
If your "two different circumstances" are different, but also quite related, then you can just have all the information that the VC needs to know as properties. You certainly don't need a whole ViewController.
For example, if your LabelViewController will show a "foo" button only if it is presented by ViewControllerFoo.
You can add a showFooButton property in LabelViewController:
var showFooButton = false
override func viewDidLoad() {
fooButton.isHidden = !showFooButton
}
And then in ViewControllerFoo.prepareForSegue:
if segue.identifier == "label" {
let vc = segue.destination as! LabelViewController
vc.showFooButton = true
}
I wouldn't call this a hack. This is the recommenced way described in this post and they didn't call it a hack.
Has anyone experienced this? I'm, not 100% certain that this is iOS12-related but calling performSegue inside didSelectRowAtIndexPath has a delay of like 1-2 secs.
I already tried different things that I found elsewhere like bringing it to the main thread but nothing works. Not sure if this is a bug or not but I haven't seen anyone talking about it online.
Try your code inside main thread:
DispatchQueue.main.async{
self.performSegue(withIdentifier: "YourSegueName", sender: self)
}
This worked for me, As sometimes we can not get e main thread which is important if you are working with some UI stuff.
Are you using the prepare Method? if so, what are you doing before the Segue?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ExampleSegue" {
let ChangeVC = segue.destination as! ExampleViewController
...
}
}
Have you tried to hand over the index path of your selected Row to a different ViewController? And decide there what to do?
I am creating an application in which there are 6 view controller in storyboard. The thing is that data is shared between the default view controller and the first one ( say A and B) which i added. i am using the prepareforseque method for passing data. the problem started when i added two more view controller. lets say C and D i created two new swift files and changed the two view controller class name. i created a textbox and button in C and label in D. when i pressed the button, the value of the text field is not passing into the D view controller although i used the same methods and code which i used for A and B. do i have to do anything else when i want to pass data between two newly added view controller.
first viewcontroller in which when a button is pressed value 1 needed to be passed:
class PlaySelectMenu: UIViewController {
var value = Int()
#IBAction func twotofive(sender: AnyObject) {
value = 1
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let nextView : PlayGameView = segue.destinationViewController as! PlayGameView
nextView.x = value
}
}
the second view controller which receive the value and print it
import Foundation
import UIKit
class PlayGameView: UIViewController{
var x = Int()
override func viewDidLoad() {
super.viewDidLoad()
print(x)
}
}
here i have added both the view controller from the object library and not working with the default one which is present in storyboard by default. i dont know why these two viewcontroller are not working. please help.
Regards Dev
One solution would be to write the data out to NSUserDefaults and then read it back from NSUserDefaults in the other view controller. Probably not the proper or correct way to share data between two view controllers, but it's been a reliable work around for me.
Other than that, you'd need to share your code so that we can see what's occurring.
Can you post also the code in your controllers C & D. And also if you have copy/paste the code inside your first two controllers into the two others, are you sure that in your prepareForSegue method you have changed the name of the destination segue ?
Assuming you have created the segue in Storyboard:
All you need is to do is put all of needed updates in prepareForSegue because twotofive is called after prepareForSegue.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
value = 1
let nextView : PlayGameView = segue.destinationViewController as! PlayGameView
nextView.x = value
}
Since you have connected your segue from button click to view controller, when you press button segue is automatically called. Instead of connecting segue from button to VC, connect VC to VC. Then in button click method at the last add below line:
#IBAction func twotofive(sender: AnyObject) {
value = 1
self.performSegueWithIdentifier("<Name of the segue identifier>", sender: self)
}
This will call your prepareForSegue. If you are calling more then one VC using segue from a VC then you can use segue.identifier to check which VC was called as below
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if segue.identifier == "CVC" {
}
I am trying to use the performSegueWithIndentifier function to to create a segue from one ViewController to the next. But, when I press the button with the UITApGestureRecognizer connected to it, the View shifts to the debugger panel.
Here is the error it is displaying:
ContaminateTargetViewController: has no segue with identifier 'showMasterChemistryViewController'
(I cut out the personal Info)
Here is the ViewControllers Class:
import Foundation
import UIKit
class ContaminateTargetViewController: UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showMasterChemistryViewController" {
let chemistryMasterViewController = segue.destinationViewController as! ChemistryMasterViewController
}
}
#IBAction func showPlaylistDetail(sender: AnyObject) {
performSegueWithIdentifier("showMasterChemistryViewController", sender: sender)
}
}
I also previously had to manual segues from the 2 buttons I have on the ViewController and recently deleted them to switch over to the UITapGestureRecognizer for the convenience. I am wondering if I have an error in my code that I do not see or if previously deleting the manual segues from the View is causing this error. If the problem is rooting from the previously deleted manual segues please tell me how to fix this in your error. If the problem is rooting form the code, please leave the code I should add, delete, or substitute.
Any suggestions or input is greatly appreciated.
Thanks in advance.
This error is called when you do not have the requested segue connected to your view controller. Are you sure that you have the two view controllers connected via a segue named "showMasterChemistryViewController" on your storyboard?
Sorry in advance that I can’t explain myself very well. I’m really new to programming and the topic of delegation still eludes me. I had some great help with this once before, but now I am trying to use a delegate in a different situation and I can’t get it right. I pieced together a bit of code that doesn’t work, and no matter how much I search I can’t find a way to fix it.
I have a view controller (MainController) with and embedded view controller (EmbeddedController) in a container view. I am trying to have a button in the embedded controller manipulate the container view (containerView).
EmbeddedController:
protocol ControllerDelegate {
func hideContainerView()
}
class EmbeddedController: UIViewController {
var delegate: VControllerDelegate?
#IBAction func button(sender: AnyObject) {
delegate?.hideContainerView()
}
}
MainController:
class MainController: UIViewController, ControllerDelegate {
#IBOutlet var containerView: UIView!
func hideContainerView() {
containerView.hidden = true
}
override func viewDidLoad() {
super.viewDidLoad()
var vc = EmbeddedController()
vc.delegate = self
}
}
Does anyone have any idea what I am doing wrong? And why this isn’t working?
What I ended up doing is adding this to the MainController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "mySegue") {
let vc = segue.destinationViewController as! EmbeddedController
vc.delegate = self
}
}
In storyboard I selected the segue from the MainController to the EmbeddedController, and set the identifier to "mySegue".
Without the code above the delegate kept returning nil. I didn't look into this solution at first as I thought segues were only for transitioning between view controllers, and in my mind I didn't see the embedded controller as a transition. Maybe someone more knowledgable than me (which is practically anyone on here at this point) can explain how this is all fitting together.
In any case, this is how I solved my issue and hopefully someone else can benefit from this as well :)
First of all, to avoid strong reference cycles:
protocol ControllerDelegate: class {
func hideContainerView()
}
class EmbeddedController: UIViewController {
weak var delegate: ControllerDelegate?
And you haven't added your newly instantiated VC view to container view, and not added it as a child VC:
let vc = EmbeddedController()
vc.delegate = self
containerView.addSubview(vc.view)
self.addChildViewController(vc)
vc.didMoveToParentViewController(self)