I am getting the error:
objc[12772]: Cannot form weak reference to instance (0x137d65240) of class appName.ViewController. It is possible that this object was over-released, or is in the process of deallocation.
I find this strange because this happened only after I have added a button click to bring the user to this UIViewController through instantiation. After running the app again, the error goes away so this occurs only when the user is segued into this UIViewController from a button.
Does anyone have any idea as to what causes this issue?
#IBOutlet weak var time: UILabel!
#IBOutlet private weak var startPause: UIButton! {
didSet {
startPause.setBackgroundColor(.green, for: .normal)
startPause.setBackgroundColor(.yellow, for: .selected)
startPause.setTitle("PAUSE".uppercased(), for: .selected)
}
}
private lazy var stopwatch = Stopwatch(timeUpdated: { [weak self] timeInterval in // SIGNAL SIGABRT
guard let strongSelf = self else { return }
strongSelf.time.text = strongSelf.timeString(from: timeInterval)
})
deinit {
stopwatch.stop()
}
#IBAction func toggle(_ sendler: UIButton) {
sendler.isSelected = !sendler.isSelected
stopwatch.toggle()
}
#IBAction func reset(_ sendler: UIButton) {
stopwatch.stop()
startPause.isSelected = false
}
private func timeString(from timeInterval: TimeInterval) -> String {
let seconds = Int(timeInterval.truncatingRemainder(dividingBy: 60))
let minutes = Int(timeInterval.truncatingRemainder(dividingBy: 60 * 60) / 60)
let hours = Int(timeInterval / 3600)
return String(format: "%.2d:%.2d:%.2d", hours, minutes, seconds)
}
Code To Present View Controller:
class TutorialViewController: UIViewController {
#IBOutlet weak var doneTut: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func doneTut(_ sender: Any) {
let homeViewController = self.storyboard?.instantiateViewController(identifier: Constants.Storyboard.homeViewController) as? HomeViewController
self.view.window?.rootViewController = homeViewController
self.view.window?.makeKeyAndVisible()
}
}
Ok, I think the way you are presenting the view controller is problematic. I would look into using UINavigationController, UITabController or your own container view controller with view controller containment to handle presentations: https://www.hackingwithswift.com/example-code/uikit/how-to-use-view-controller-containment
Basically, since you are resetting the window's root view controller, your presenting view controller, TutorialViewController, might be getting deallocated as a result (if nothing else is retaining it).
Related
import UIKit
//EventList ViewController
class EventPage: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
class EventForm: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
//IBOutlets
#IBOutlet weak var eventNameField: UITextField!
#IBOutlet weak var fromTimePicker: UIDatePicker!
#IBOutlet weak var toTimePicker: UIDatePicker!
#IBOutlet weak var colorPreview: UIView!
#IBAction func cancel(_ sender: Any) {
//empty text field
eventNameField.text = ""
}
#IBAction func save(_ sender: Any) {
if (eventNameField.hasText) {
//fix error handling
eventNameField.backgroundColor = UIColor.systemGray2
//pull data from fields
let text = eventNameField.text!
let fromTime = fromTimePicker.date
let toTime = toTimePicker.date
//initialize object
let currentEvent = EventModel(eventName: text, fromTime: fromTime, toTime: toTime, color: storedColor)
//append to data model
EventDataArray.append(currentEvent)
//transition
present(EventPage(), animated:true)
}
else {
eventNameField.backgroundColor = UIColor.systemRed
}
}
}
I currently have an EventPage class declared as type UIViewController, but upon pressing the save button with a populated text field a transition to a blank ViewController occurs. I've attached the class to the correct ViewController in main.storyboard.
The problem in here is that you are creating a new EventPage but it doesn't inherit from Storyboard.
1
Go to the inspector in your storyboard, select your View Controller, and write an identifier for your View Controller (can be anything)
Write it in Storyboard ID:
2
Replace
present(EventPage(), animated:true)
With
(don't forget to replace 'MYIDENTIFIER' with the id you entered earlier)
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MYIDENTIFIER") as! EventPage
// If you need to do any configurations to your view controller, do that in here.
// For example:
// viewController.label.text = "Hello, world!"
present(viewController, animated:true)
Note
If the name of your Storyboard file name is not called Main, replace "Main" in step 2 with the name of your storyboard file (excluding .storyboard)
I got a main VC called "ViewController.swift", which is bond to my ViewController Scene in Main.storyboard.
Then I want another screen, which shows when a condition in my ViewController.swift is fulfilled.
Therefore I got a Gameover.storyboard, which has a VC Scene with Storyboard ID "overID" as well as a "GameoverViewController.swift" to handle the GameOver Scene.
Here is my code in ViewController.swift, which should open Gameover.storyboards VC Scene when the condition you see below is fulfilled:
import UIKit
class ViewController: UIViewController {
var wantedLiter = 10.00
var timer : Timer?
var actualLiter = 0.00
var timerReset = true
var preisProLiter = 1.26
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
let storyBoard: UIStoryboard = UIStoryboard(name: "Gameover", bundle: nil)
let gameoverViewController = storyBoard.instantiateViewController(withIdentifier: "overID") as! GameoverViewController
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#IBOutlet weak var preisliterLabel: UILabel!
#IBOutlet weak var euroLabel: UILabel!
#IBOutlet weak var numberLabel: UILabel!
#IBOutlet weak var numberButton: UIButton!
#IBOutlet weak var confirmButton: UIButton!
#IBAction func confirm(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
numberLabel.text = String(round(1000*actualLiter)/1000) //initial value
preisliterLabel.text = String(preisProLiter)
// Do any additional setup after loading the view.
}
#IBAction func holdingTheButton(_ sender: Any) {
print("I am holding")
timerReset = false // reset to false since you are holding the button
guard timer == nil else { return }
timer = Timer.scheduledTimer(timeInterval: 0.005, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}
#IBAction func buttonReleased(_ sender: Any) {
print("button released")
if (wantedLiter == actualLiter){
print("WIN!")
actualLiter = 0;
}
//startTime = 0.00
timer?.invalidate()
timer = nil
// timerReset = true // reset to true since you released.
}
#objc func updateTime(){
//update label every second
print("updating label ")
if (wantedLiter <= actualLiter){
print("Ooops")
actualLiter = 0;
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
self.present(gameoverViewController, animated: true, completion: nil)
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}
actualLiter += 0.01
numberLabel.text = String(round(1000*actualLiter)/1000)
euroLabel.text = String((round(100*actualLiter*preisProLiter)/100))
}
}
And now got you some Screenshots, which might help.
If you need more Screens, please ask.
Right now, I got following errors:
1) ViewController.swift in the upper part marked with "!!!!..."
Cannot use instance member 'storyBoard' within property initializer; property initializers run before 'self' is available
2) ViewController.swift
Cannot convert value of type 'gameoverViewController.Type' to expected argument type 'UIViewController'
3) ViewController.swift upper Part:
Use of undeclared type 'GameoverViewController'
This is how I changed the code:
import UIKit
class ViewController: UIViewController {
var wantedLiter = 10.00
var timer : Timer?
var actualLiter = 0.00
var timerReset = true
var preisProLiter = 1.26
let storyBoard: UIStoryboard = UIStoryboard(name: "Gameover", bundle: nil)
var gameoverViewController: GameoverViewController!
#IBOutlet weak var preisliterLabel: UILabel!
#IBOutlet weak var euroLabel: UILabel!
#IBOutlet weak var numberLabel: UILabel!
#IBOutlet weak var numberButton: UIButton!
#IBOutlet weak var confirmButton: UIButton!
#IBAction func confirm(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
gameoverViewController = storyBoard.instantiateViewController(withIdentifier: "overID") as? GameoverViewController
numberLabel.text = String(round(1000*actualLiter)/1000) //initial value
preisliterLabel.text = String(preisProLiter)
// Do any additional setup after loading the view.
}
#IBAction func holdingTheButton(_ sender: Any) {
print("I am holding")
timerReset = false // reset to false since you are holding the button
guard timer == nil else { return }
timer = Timer.scheduledTimer(timeInterval: 0.005, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}
#IBAction func buttonReleased(_ sender: Any) {
print("button released")
if (wantedLiter == actualLiter){
print("WIN!")
actualLiter = 0;
}
//startTime = 0.00
timer?.invalidate()
timer = nil
// timerReset = true // reset to true since you released.
}
#objc func updateTime(){
//update label every second
print("updating label ")
if (wantedLiter <= actualLiter){
print("Ooops")
actualLiter = 0;
self.present(gameoverViewController as! UIViewController, animated: true, completion: nil)
}
actualLiter += 0.01
numberLabel.text = String(round(1000*actualLiter)/1000)
euroLabel.text = String((round(100*actualLiter*preisProLiter)/100))
}
}
(not working)
And I changed the GameoverViewController type to UIViewController.
But where was my Typo?? (G instead of g)?
Move the initializer for gameoverViewController into viewDidLoad or the like and make gameoverViewController an optional or implicitly unwrapped optional (that's the ! after a type)
Should be fixed by 1.
Please show the source for GameoverViewController if this is not fixed by 1. too.
I'm doing a test of a custom keyboard. This is what I need:
It has to have two UITextFields. Cannot be labels.
The keyboard is an embedded UIView.
The default keyboard should be disabled.
It cannot be a keyboard extension.
Not sure why the app is crashing. PS: Not all the keys are on the code yet. Here is an image of what I'm trying to do and the two View Controllers.
Edit: The error is: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
First ViewController:
import UIKit
class HomeVC: UIViewController, ButtonTapDelegate {
#IBOutlet var textField1: UITextField!
#IBOutlet var textField2: UITextField!
#IBOutlet var keyboardView: UIView!
var buttonPressed = [String]()
override func viewDidLoad() {
addKeyboard(view: keyboardView)
buttonPressed = [String]()
textField1.inputView = UIView()
textField2.inputView = UIView()
}
func addKeyboard(view: UIView) {
let keyboard = KeyboardVC(nibName: "KeyboardVC", bundle: nil)
view.addSubview(keyboard.view)
addChild(keyboard)
}
func didTapButton(sender: UIButton) {
if sender.tag == 5 {
textField1.text?.append(contentsOf: " ")
} else if sender.tag == 6 {
textField1.text?.removeAll()
buttonPressed = [String]()
} else {
let val = sender.titleLabel?.text
textField1.text?.append(contentsOf: val!)
}
self.textField1.text = buttonPressed.joined(separator: "")
}
}
Here is the second View Controller:
import UIKit
protocol ButtonTapDelegate {
func didTapButton(sender: UIButton)
}
class KeyboardVC: UIViewController {
var delegate: ButtonTapDelegate!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttons(_ sender: UIButton) {
delegate.didTapButton(sender: sender)
print(sender)
}
}
var delegate: ButtonTapDelegate!
An implicitly unwrapped optional is essentially a promise that you're definitely going to give the variable a value before you try to access it. The problem in this case is that you haven't done that. Most likely, you want to do this in your first view controller:
func addKeyboard(view: UIView) {
let keyboard = KeyboardVC(nibName: "KeyboardVC", bundle: nil)
keyboard.delegate = self // Now "delegate" will have a value before the function gets called
view.addSubview(keyboard.view)
addChild(keyboard)
}
I am doing a Mac application, and I have a problem appending text to a NSScrollView when I call a function from a different class.
I have this function on my ViewController class:
import Cocoa
class PopoverVC1: NSViewController {
let popover1 = NSPopover()
class func loadView() ->PopoverVC1 {
let vc = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"),
bundle: nil).instantiateController(withIdentifier:
NSStoryboard.SceneIdentifier(rawValue: "Popover1")) as! PopoverVC1
vc.popover1.contentViewController = vc
return vc
}
override func viewDidLoad() {
super.viewDidLoad()
popover1.behavior = .transient
popover1.contentViewController = self
}
func showPopover (view: NSView){
popover1.show(relativeTo: view.bounds, of: view, preferredEdge: .maxY)
}
#IBOutlet weak var radioOption1: NSButton!
#IBOutlet weak var radioOption2: NSButton!
#IBOutlet weak var radioOption3: NSButton!
#IBAction func clickOption(_ sender: NSButton) {
switch sender {
case radioOption1: popover1.performClose(sender)
case radioOption2: let vc = ViewController()
vc.myPrint(string: "This is a test")
default: print ("hello")
}
}
}
Than I have a PopoverVC1 class, which is a class to a popover I am using:
import Cocoa
class ViewController: NSViewController {
#IBOutlet weak var oneYes: NSButton!
#IBOutlet weak var oneNo: NSButton!
#IBOutlet weak var notesArea: NSScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded
}
}
func myPrint (string: String){
let mystring = string
let myNotes = notesArea.documentView as? NSTextView
let text = myNotes?.textStorage!
let attr = NSAttributedString(string: mystring)
text?.append(attr)
}
let popover1 = NSPopover()
#IBAction func oneClicked(_ sender: NSButton) {
switch sender {
case oneYes: let vc = PopoverVC1.loadView()
vc.showPopover(view: sender)
case oneNo:
let myNotes = notesArea.documentView as? NSTextView
let text = myNotes?.textStorage!
let attr = NSAttributedString(string: "test")
text?.append(attr)
default: print ("")
}
}
}
However, I got an error when I press the radio button "oneNo" that should call the function "myPrint" and pass the argument.
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
I did some tests and when I call this same function "myPrint" from within the ViewCotroller class it works fine.
Any ideas?
Your issue is in clickOption when you are calling:
let vc = ViewController()
vc.myPrint(string: "This is a test")
When you call this method from code and the ViewController's UIViews are set up in a storyboard, the connection from the storyboard is not made. That is why the notesArea is nil when you call the function myPrint. In this case you are creating a new copy of ViewController and it will not be the same one that created the popover.
There are a few ways you can solve the problem that you are trying to accomplish. One of them is known as a delegate. This is a way for you to to call the ViewController's methods like your popover inherited them. You can check out a tutorial here. The idea is that we want to have a reference to the ViewController in your popover so that you can call the functions in the protocol. Then the ViewController that conforms to the protocol will be responsible for handling the method call.
So let's create a protocol called PrintableDelegate and have your ViewController class conform to it. Then in your popover, you will be able to have a reference to the ViewController as a weak var called delegate (you can use what ever name you want but delegate is standard). Then we can call the methods described in the protocol PrintableDelegate, by simply writing delegate?.myPrint(string: "Test"). I have removed some of your irrelevant code from my example.
protocol PrintableDelegate {
func myPrint(string: String)
}
class ViewController : UIViewController, PrintableDelegate {
func myPrint (string: String){
let mystring = string
let myNotes = notesArea.documentView as? NSTextView
let text = myNotes?.textStorage!
let attr = NSAttributedString(string: mystring)
text?.append(attr)
}
#IBAction func oneClicked(_ sender: NSButton) {
let vc = PopoverVC1.loadView()
// Set the delegate of the popover to this ViewController
vc.delegate = self
vc.showPopover(view: sender)
}
}
class PopoverVC1: NSViewController {
// Delegates should be weak to avoid a retain cycle
weak var delegate: PrintableDelegate?
#IBAction func clickOption(_ sender: NSButton) {
// Use the delegate that was set by the ViewController
// Note that it is optional so if it was not set, then this will do nothing
delegate?.myPrint(string: "This is a test")
}
}
I have a view controller embedded in another VC.
I would like to get the value of a variable from the main VC inside the embedded one. Specifically, I would like to change the text of label2 based on the value of label1.
I tried with "prepareForSegue", but it seems it's not triggered for embedded view controllers. I tried to isolate the problem in a test project:
Code for main VC:
class MyViewController: UIViewController {
#IBOutlet weak var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Hello"
}
}
Code for embedded VC:
class EmbeddedVC: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Thanks for your help :)
A way to achiŠµve this is to get the child view controller instance in the parent's viewDidLoad. It appears that the parent's viewDidLoad: gets called after the child's viewDidLoad:, which means the label is already created in the child's view.
override func viewDidLoad() {
super.viewDidLoad()
if let childVC = self.childViewControllers.first as? ChildVC {
childVC.someLabel.text = "I'm here. Aye-aye."
}
}
First of all you can't set directly EmbeddedVC's lable2.text In prepareForSegue
because call sequence following below
MainVC's prepareForSeque this time EmbeddedVC's label2 is nil
EmbeddedVC's viewDidLoad called then label2 loaded
MainVC's viewDidLoad called then label1 loaded
so if you assign MainVC's label1.text to EmbeddedVC's label2.text in prepareForSeque
both label1 and label2 are nil so did not work
There are two way to solve this question
First Solution
MainViewController has EmbeddedVC and when MainVC's viewDidLoad called, assign label1.text to embeddedVC.label2.text
class MyViewController: UIViewController {
#IBOutlet weak var label1: UILabel!
var embeddedVC: EmbeddedViewController? = nil
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Hello"
embeddedVC?.label2.text = label1.text
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let embeddedVC = segue.destination as? EmbeddedViewController {
self.embeddedVC = embeddedVC
}
}
}
class EmbeddedViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
Second Solution, use protocol and get MainVC's label text when viewWillAppear or viewDidAppear (later viewDidLoad called)
protocol EmbeddedVCDelegate: class {
func labelText() -> String?
}
class MyViewController: UIViewController, EmbeddedVCDelegate {
#IBOutlet weak var label1: UILabel!
// MARK: EmbeddedVCDelegate
func labelText() -> String? {
return label1.text
}
override func viewDidLoad() {
super.viewDidLoad()
label1.text = "Hello"
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let embeddedVC = segue.destination as? EmbeddedViewController {
embeddedVC.delegate = self
}
}
}
class EmbeddedViewController: UIViewController {
#IBOutlet weak var label2: UILabel!
weak var delegate: EmbeddedVCDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
override viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
label2.text = delegate?.labelText()
}
}
You should try to use prepareForSegue like this:
if segue.identifier == "identifier" {
guard let destinationViewController = segue.destination as? VC2 else { return }
destinationViewController.label2.text = mytext
}
Where the segue identifier you assign in storyboard