NSLayoutConstraint: Found nil while unwrapping optional Value - swift

having a bit of a problem here, so I was trying to cal a function of which it will change the height constraint constant, but it gives me an error :
"NSLayoutConstraint: Found nil while unwrapping optional Value"
Another catch is I can change the constant from the ViewDidLoad() function without any errors, while calling the function from another controller gives me the error.
I tried to clean the project, delete the outlet and re-outlet it again. No luck.
Hope you can help me, thanks!
MainController
var bottomContainerCon: CGFloat = 80
#IBOutlet var bottomContainerHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
bottomContainerHeight.constant = bottomContainerCon
}
func changeHeight() {
bottomContainerHeight.constant = self.bottomContainerCon
self.view.layoutIfNeeded()
}
PagerController
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = self.storyboard?.instantiateViewController(withIdentifier: "MainNeedPage") as! NeedDetailsController
let currentIndex = pages.index(of: previousViewControllers.first!)!
let nextIndex = abs((currentIndex + 1) % pages.count)
if(nextIndex == 0) {
vc.changeHeight(value:80)
} else {
vc.changeHeight(value:250)
}
override func viewDidLayoutSubviews() {
self.bottomContainerHeight.constant = self.bottomContainerCon
self.view.layoutIfNeeded()
}
Here's the storyboard Storyboard

Since you instantiate the VC , then the outlet is nil until view loads
#IBOutlet var bottomContainerHeight: NSLayoutConstraint!
you need to do this
var bottomContainerCon: CGFloat = 0
then assign the value inside
viewDidLoad / viewDidLayoutSubviews like this
bottomContainerHeight.constant = bottomContainerCon

Related

Cannot Form Weak Reference To Instance Of Class - Swift Error

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).

Custom keyboard is crashing the app - Swift

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)
}

How can I transfer UISlider value on SecondViewController to Alarm on FirstViewController?

I have two ViewControllers, FirstViewController and SecondViewController.
And there is UISlider on SecondViewController. There is an alarm on FirstViewController.
Now I want to transfer UISlider value, which is volume to alarm on FirstViewController.
ps. The function what I want to make is totally same as default iPhone setting slider.
I would be so happy so if you guys give me your knowledge, please.
import UIKit
import AVFoundation
class FirstViewController: UIViewController, AVAudioPlayerDelegate,
UITableViewDelegate, UITableViewDataSource{
let TODO = ["A", "B", "C"]
let notificationCenter = NotificationCenter.default
var volume = Float()
var counter = 0
var timer = Timer()
var startTime:Double = 0.0
var audioPlayer: AVAudioPlayer!
#IBOutlet weak var tableView: UITableView!
#IBAction func firstSwitch(_ sender: UISwitch)
{
if (sender).isOn
{
timer = Timer.scheduledTimer(withTimeInterval: 1 * 1, repeats: false, block: { timer in
self.audioPlayer.play()
self.audioPlayer.numberOfLoops = -1
print(self.audioPlayer.isPlaying)
})
}else{
timer.invalidate()
print("switch1stopped")
self.audioPlayer.stop()
}
}
notificationCenter.addObserver(self, selector: #selector(catchNotification(notification:)), name: NSNotification.Name(rawValue: "test"), object: nil)
}
#objc func catchNotification(notification: Notification) -> Void {
print("Catch notification")
audioPlayer.volume = volumeChane.value
//Use of unresolved identifier 'volumeChane'
}
///////////////////////////////////////////////////////////////////////////
import UIKit
import AVFoundation
class SecondViewController: UIViewController {
var audioPlayer: AVAudioPlayer!
let notificationCenter = NotificationCenter.default
#IBOutlet weak var volumeSlider: UISlider!
#IBOutlet weak var volumeLabel: UILabel!
#IBAction func volumeChange(_ sender: UISlider)
{
volumeLabel.text = String(Int(sender.value))
volumeSlider.value = sender.value
audioPlayer.volume = volumeSlider.value
notificationCenter.post(name: NSNotification.Name(rawValue: "test"), object: nil)
audioPlayer.play()
}
override func viewDidLoad()
{
super.viewDidLoad()
if let url=Bundle.main.url(forResource:"Alarm",withExtension:".mp3" )
{
do {
audioPlayer = try AVAudioPlayer(contentsOf:url)
audioPlayer?.play(atTime:1 * 10)
}catch{
audioPlayer = nil
}
}else{
fatalError("Url is nil")
}
}
extension Notification.Name
{
static let myNotificationName = Notification.Name("test")
}
Using NSNotificationCenter for a simple task such as this might be an overkill. You usually use closures to catch data changes in your components or view controllers.
In the SecondViewController create a variable containing a closure:
var onVolumeChange: ((value: Float) -> Void)?
Call it in the IBAction that monitors slider's onChange event.
#IBAction func volumeChange(_ sender: UISlider)
{
self.onVolumeChange?(sender.value)
}
Pass the onVolumeChange closure from the FirstViewController when navigating to the second one. I'm not sure how you perform navigation so I'll assume you do it programmatically.
let vc = UIStoryboard(name: "main", bundle: nil).instantiateViewController(withIdentifier: "SecondViewController")
vc.onVolumeChange = { value in
audioPlayer.volume = value
}
self.navigationController?.pushViewController(vc, animated: true)

Append text to NSScrollView - Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

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")
}
}

Add value to function from other controller

I have this view controller with function: updateProgressComplete. But I can't add values to the view controller where this function is in.
My view controller:
import UIKit
class StepsDetailViewController: UIViewController {
var pageIndex: Int! = 0
var titleText: String!
var stepData: String!
var pageControl: PageControl?
let screenSize: CGRect = UIScreen.mainScreen().bounds
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var stepsLabel: UILabel!
var g : ShapeView?
override func viewDidLoad(){
super.viewDidLoad()
self.dateLabel.text = self.titleText
self.stepsLabel.text = self.stepData
g = ShapeView(origin: CGPoint(x: UIScreen.mainScreen().bounds.width / 2, y: UIScreen.mainScreen().bounds.height / 2))
self.view.addSubview(g!)
}
func updateProgressComplete(newValue : CGFloat, view: StepsDetailViewController) {
self.dateLabel.text = "hello" <-- this doesn't work
}
}
I think this isn't working because of updateProgressComplete comes from another class:
import UIKit
class PageControl: BasePageViewController, UIPageViewControllerDataSource {
var pageViewController: UIPageViewController!
var pageTitles: [[String]]!
var complete : CGFloat = 0.0;
var count = 0
override func viewDidLoad(){
super.viewDidLoad()
HealthKit().recentSteps() { steps, error in
self.pageTitles = steps;
self.count = self.pageTitles.count
self.count -= 1
self.pageViewController = self
self.pageViewController.dataSource = self
let startVC = self.viewControllerAtIndex(self.count) as StepsDetailViewController
let viewControllers = NSArray(object: startVC)
print(viewControllers)
dispatch_async(dispatch_get_main_queue(), {
self.pageViewController.setViewControllers(viewControllers as? [UIViewController], direction: .Forward, animated: true, completion: nil)
})
}
}
override func percentCompleteDidChange() {
if let percentComplete = self.percentComplete {
complete = percentComplete
let vc: StepsDetailViewController = self.storyboard?.instantiateViewControllerWithIdentifier("StepsDetailViewController") as! StepsDetailViewController
vc.updateProgressComplete(percentComplete, view: vc)
}
}
func viewControllerAtIndex(index: Int) -> StepsDetailViewController
{
if ((self.pageTitles.count == 0) || (index >= self.pageTitles.count)) {
return StepsDetailViewController()
}
let vc: StepsDetailViewController = self.storyboard?.instantiateViewControllerWithIdentifier("StepsDetailViewController") as! StepsDetailViewController
vc.titleText = self.pageTitles[index][0]
vc.stepData = self.pageTitles[index][1]
vc.pageIndex = index
return vc
}
// MARK: - Page View Controller Data Source
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
let vc = viewController as! StepsDetailViewController
var index = vc.pageIndex as Int
if (index == 0 || index == NSNotFound)
{
return nil
}
index -= 1
return self.viewControllerAtIndex(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let vc = viewController as! StepsDetailViewController
var index = vc.pageIndex as Int
if (index == NSNotFound){
return nil
}
index += 1
if (index == self.pageTitles.count)
{
return nil
}
return self.viewControllerAtIndex(index)
}
}
How can I fix this? I think it has something to do that I use this: instantiateViewControllerWithIdentifier? The value at the updateProgressComplete changes all the time, that's why I use that function and don't pass it as variable.
You're right, instantiateViewControllerWithIdentifier is a problem. If creates a new object. ('new' as in different from the one you want to update.) However you create the PageControl, try defining a delegate for it and setting StepsDetailViewController as the delegate so that you have the correct object to update.