When adding a subview, the view controller seems to leak.
Why does the following print 'What'
import UIKit
final class ViewController: UIViewController {
private lazy var mySwitch: UISwitch = {
let mySwitch = UISwitch()
mySwitch.tintColor = .blue
return mySwitch
}()
func setup() {
view.addSubview(mySwitch)
}
#objc func switchChangedState() {
}
deinit {
print("what")
}
}
var controller: ViewController? = ViewController()
controller = nil
But the following does not
var controller: ViewController? = ViewController()
controller?.setup()
controller = nil
Edit: adding GIF
Xcode Version 9.4.1 (9F2000)
Your code is good. controller?.setup() will not cause a leak. Please make sure the code in test case 2 is really called or not. (No calling no "what" printed)
There's nothing wrong with your code. There's no retain cycle here.
The problem appears to be something to do with the playground. It could be a bug, or the playground may be retaining your view controller for some reason.
If you execute your code in an actual Xcode project (either in the iOS simulator or on a device), the initializer is executed in both cases:
Related
I want to know what was the name of the viewController the the button was tapped. I've already looked into the answer provided here. But I believe won't work if I have different containerViews on the screen...where the viewController for each button may be different. Hence I need a different solution.
So I wrote this function to recursively look until it finds a UIViewController.
extension UIView{
func getTypeOfMyViewController() -> UIViewController.Type?{
if let _super = superview as? UIViewController{ // logically wrong!
return type(of:_super)
}else if let _super = superview{
return _super.getTypeOfMyViewController()
}else{
assertionFailure("everything should end up being on some viewController")
return nil
}
}
}
The only problem is this line:
if let _super = superview as? UIViewController{
It gives me the following warning:
Cast from 'UIView?' to unrelated type 'UIViewController' always fails
superview is a UIView and I don't know how to extract the 'viewController' which contains the 'view'.
Question1: So How can I do that?
Additionally I would like to use the getTypeOfMyViewController function as such:
extension UIButton{
open override var accessibilityLabel: String?{
get {
return "\(getTypeOfMyViewController.self): \(titleLabel?.text ?? "Null")"
}
set{
// nothing particular
}
}
}
I'm doing this because I want to create a unique identifier for all button taps in my logging system.
Question2: Does Swift offer any easier solution to this?
A view controller is not a view, so it can never be a superview. You have the right idea, but you're looking at the wrong hierarchy. What you want is not the view hierarchy but the responder chain.
Walk up the responder chain until you come to the view controller:
var r : UIResponder = theButton
repeat { r = r.next! } while !(r is UIViewController)
let vc = r as! UIViewController
I'm dealing with some deallocation issue and perhaps strong or circular referencing that can't figure out. I have three UIViews instantiating like below:
There is one main ViewController which I have added a UIView inside it in storyboard and the UIView has a weak outlet inside the class like:
class ViewController : UIViewController {
//MARK: - outlets
#IBOutlet weak var firstView: FirstUiview!
}
second UIView is added as a subview to the first view programmatically like:
class FirstUiview : UIView {
//creating an instance of secondUiView
lazy var mySecondView: SecondViewClass = {
let dv = SecondViewClass()
dv.backgroundColor = UIColor.red
return dv
}()
//sometime later by clicking on a button
self.addSubview(mySecondView)
//a button will be tapped to remove mySecondView;
//later will be called at some point upon tapping:
func removingSecondViewByTapping() {
if mySecondView.isDescendant(of: self) {
mySecondView.removeFromSuperview()
}
}
}
Now the SecondViewClass is :
class SecondViewClass : UIView {
//in this class I create bunch of uiview objects like below:
lazy var aView : UIView = {
let hl = UIView()
hl.tag = 0
hl.backgroundColor = UIColor.lightGray
return hl
}()
self.addSubview(aView) //... this goes on and I add other similar views the same way.
//creating an instance of thirdView
var let thirdView = UIView()
self.addSubview(thirdView)
}
Now if user taps the button to remove mySecondView and then add it again at some other time (still in the same ViewController) I expect all the subviews of mySecondView to have been released and gone but they are all there. I would appreciate it a lot if someone can point it to me where am I keeping a strong reference or if there is a circular referencing issue? or perhaps something else?
You have two strong references to your views, your custom property and the view hierarchy reference established when you call addSubview. When you remove the view from the view hierarchy, your class, itself, still has its strong reference to it.
You could solve this by making your reference optional, and when you call removeFromSuperview, also manually set your reference to nil. Or, perhaps easier, you might resolve this by using weak references, letting the view hierarchy maintain the strong references for you. And because your custom property is weak, when you remove it from the view hierarchy (thus eliminating the only strong reference to it), your weak reference will automatically become nil:
class FirstView: UIView {
weak var secondView: SecondView? // note the `weak` reference, which is obviously an optional
//sometime later by clicking on a button
func doSomething() {
let subview = SecondView()
subview.backgroundColor = .red
self.addSubview(subview)
secondView = subview
}
// a button will be tapped to remove secondView;
// later will be called at some point upon tapping ...
func removingSecondViewByTapping() {
secondView?.removeFromSuperview()
}
}
I'm trying to make a function that I can call on to update my UILabel's scoreLabel.text. However, I get an error whenever I try to change it. What's confusing to me is that I don't receive an error when changing it inside viewDidLoad(). Everywhere else returns the following error:
In the console I also get error:
fatal error: unexpectedly found nil while unwrapping an Optional value.
So what I've been lead to believe is that when calling my function to update the text, the view hasn't loaded the UILabel yet. But I'm certain that this function is called only once the view has loaded.
Things I've checked/tried for:
That my IBOutlet is properly connected
That my function is being called
Using both scoreLabel.text and self.scoreLabel.text
Using a Strong and Weak outlet connection
I am also positive that changeTextLabel is being called after scoreLabel is loaded into memory. But again my error seems to say otherwise.
Here's a complete markup of my code. I've removed some irrelevant details for readability:
import UIKit
import SpriteKit
class GameViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
//This is where all my other code was
print(scoreLabel.text)
scoreLabel.text = "Testing" //This line can be run
}
func changeTextLabel() {
print("changeTextLabel called")
if self.scoreLabel != nil {
self.scoreLabel.text = "yay"
} else {
print("scoreLabel is nil") //This is called every time
}
}
}
Thanks for your time
Edit:GameScene.swift
This is only the part that should be of concern
func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
print("Hit")
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let object = storyBoard.instantiateViewController(withIdentifier: "GameViewController") as! GameViewController
object.changeTextLabel()
projectile.removeFromParent()
monster.removeFromParent()
}
I dont fully know your project setup but your approach seems complicated, its not what you should do in SpriteKit games. So I dont think its a good idea to tell you how to fix your current problem.
You should create your UI, such as labels, buttons etc using SpriteKit APIs (SKLabelNodes, SKSpriteNodes, SKNodes etc) directly in the relevant SKScene(s).
Your GameViewController should only really handle presenting SKScenes. so there should be next to no code there apart from loading the 1st SKScene.
If you have multiple SKScenes (MenuScene, ShopScene, SettingScene etc) your approach will fall apart because the score label will show in all SKScenes. GameViewController presents all your SKScenes, so whats added to GameViewController shows in all SKscenes. That means you have to remove/add labels and other UI for each scene and it will be chaos.
So to create a score label you should do this directly in the relevant scene. I like to use the lazy var approach to keep the setup code for the label in the same spot.
class GameScene: SKScene {
lazy var scoreLabel: SKLabelNode = {
let label = SKLabelNode(fontNamed: "HelveticaNeue")
label.text = "SomeText"
label.fontSize = 22
label.fontColor = .yellow
label.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
return label
}()
override func didMove(to view: SKView) {
addChild(scoreLabel)
}
}
Than you can update it like this directly in the collision code you have.
scoreLabel.text = "NewText"
Now when you change from 1 SKScene to another SKScene you dont have to worry about removing this label, SpriteKit will do it for you.
You could also use xCodes level editor to add this label visually, similar to storyboards. This brings me to my final suggestion, which is that storyboards are not really used in SpriteKit games. Storyboards are for ViewControllers and in SpriteKit you are working with SKScenes.
Hope this helps
The problem is with this line,
let game = GameViewController()
game.changeTextLabel()
You should create GameViewController object using XIB or Storyboard.
In your case it is just creating object using .swift file, formally class.
You should create your object using View which is having IBOutlet connection with scoreLabel.
Try (If using Storyboard),
let storyboard = UIStoryboard(name: "YourStoryBoard", bundle: nil)
let gameVC = storyboard.instantiateViewController(withIdentifier: "GameViewController") as! UIViewController
or (If using XIB)
var gameVC = GameViewController(nibName: "GameViewController", bundle: nil)
Update you changeTextLabel with following (Ref),
func changeTextLabel() {
print(self.view)
print("changeTextLabel called")
if self.scoreLabel != nil {
self.scoreLabel.text = "yay"
} else {
print("scoreLabel is nil") //This is called every time
}
}
Replace this line
self.scoreLabel.text = "yay"
With
self.scoreLabel?.text = "yay"
Make these changes and try again:
func changeTextLabel() {
print("changeTextLabel called")
if self.scoreLabel != nil {
self.scoreLabel.text = "yay"
}
}
func projectileDidCollideWithMonster(projectile: SKSpriteNode, monster: SKSpriteNode) {
print("Hit")
let game = STORYBOARD.instantiateViewControllerWithIdentifier("STORYBOARD_IDENTIFIER") as! GameViewController
game.changeTextLabel()
projectile.removeFromParent()
monster.removeFromParent()
}
It happens because you are not getting memory of label in any other function.It happens because of so many reasons.
One reason could be #IBOutlet weak var scoreLabel: UILabel!
Try to create a strong variable outlet.
#IBOutlet strong var scoreLabel: UILabel!
Second reason could be that you are calling changeTextLabel before the label gets memory. So call it later.
[Problem soluted!Just want to know why there is such a difference in ios8 and ios9] I was making a register view controller these days and face with some problem about weak reference.
and below is some part of the code(swift)
problem come when I use an iphone6 ios8.1
it crashed. And then I noticed that the weak reference is not proper here. But the code runs well in my ios9 iphone6s. I ran this code on an iphone6 ios8 simulator, app crashed. So I think there is some thing different in processing weak reference in ios8 and ios9, But who can explain why..?
class VC: UIViewController {
weak var verifyTextField: UITextField?
override func viewdidload() {
//....
verifyTextField = newTextField();
view.addSubview(verifyTextField!);
}
func newTextField() -> UITextField {
let ntf = UITextField();
//do some settings to ntf;
return ntf;
}
}
You set your new UITextField instance to the weak var verifyTextField but before you add it as a subview (which increments the retain count) it is deallocated (the count is 0 since the var is weak) so verifyTextField! crashes, the crash you're getting is most likely the famous
Unexpectedly found nil while unwrapping an Optional
It's easy to fix it
Don't use a weak var
Don't force unwrap (use if let instead)
The code should be as follows:
class VC: UIViewController {
var verifyTextField: UITextField? //should not be weak
override func viewdidload() {
//....
verifyTextField = newTextField()
if let verifyTextField = verifyTextField {
view.addSubview(verifyTextField!)
}
}
func newTextField() -> UITextField {
let ntf = UITextField()
//do some settings to ntf
return ntf
}
}
Looks like your object is deallocated instantly after initialization because you don't store any strong reference for it.
Try this code:
override func viewdidload() {
//....
let verifyTextField = newTextField();
view.addSubview(verifyTextField);
self.verifyTextField = verifyTextField;
}
Also no need to use weak reference here, because verifyTextField doesn't have reference to your VC, so you won't get a retain cycle.
I want to update the label in the DetailViewController everytime I selected a tableRow in the MasterViewController. To achieve this, I designed a delegate, which I have in the MasterVC
protocol TestTableViewControllerDelegate {
func selectedRow(selectedCar : Car)
}
class TestTableViewController: UITableViewController {
...
var delegate : TestTableViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = DetailViewController()
The delegate works just fine, (it is implemented correctly in the DetailVC), it can pass values from TestTableVC to DetailVC and also correctly do println(), which prints a new Car.model String to the console every time I select a row in the TTVC.
The DetailVC looks like this (shortened):
class DetailViewController: UIViewController, TestTableViewControllerDelegate {
#IBOutlet var textLabel: UILabel!
var theCar : Car? {
didSet(newCar) {
refreshUI()
}
}
override func viewDidLoad() {
super.viewDidLoad()
refreshUI()
}
func selectedRow(selectedCar : Car) {
theCar = selectedCar
refreshUI()
}
func refreshUI() {
textLabel?.text = theCar!.model
}
}
I can achieve any kind of action with my delegate, expect for refreshing the UI. I have tried numerous ways, this is my latest attempt. Before that, I tried setting the textLabel's text property directly within the delegate method, didn't work. This problem only occurs when working with the UI-elements. I know it has something to do with the view not being loaded yet, but why does my refreshUI() function not work at all?
I am still a beginner, so any tip or help would be much appreciated!
A workaround I've used is to cerate a properly in the delegate and pass the value to it instead of the UI element. When the view loads I update the label's text properly with the value of the delegates property. I would think there's a better way to do this (I'm new to programming) but this is the best soultion I've come up with so far. Will update with sample code soon.