'Unexpectedly found nil while unwrapping an Optional value' - swift

I'd like to do a label which is present only when button is touched.
When function hidden() is called in MainViewController It's working well but when i'm calling it from ButtonAction class (same function) I get an classic error:
Unexpectedly found nil while unwrapping an Optional value
Here's the code:
// MainViewController.swift
import UIKit
class MainViewController: UIViewController {
#IBOutlet weak var labelToShow: UILabel!
#IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
labelToShow.isHidden = true
}
func hidden() {
labelToShow.isHidden = true
}
func inHidden() {
labelToShow.isHidden = false
}
}
AND :
// ButtonAction.swift
import UIKit
class ButtonAction: UIButton {
var touched:Bool = false
var mainScreen = MainViewController()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
sleep(1)
mainScreen.hidden()
touched = true
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
mainScreen.inHidden()
touched = false
}
}

The mainScreen instance created with the default initializer MainViewController() is different from those designed in Interface Builder.
You need the actual reference to the main view controller (via IBOutlet or protocol / delegate etc.)

In that line of code you are creating a new instance of your MainViewController :
var mainScreen = MainViewController()
You have to get the reference of your MainViewController() instance (created automatically by storyboard)

Related

On pressed touch Hide/Show UIImage, and count number down from 10 to 0

On every pressed touch I want UILabel to count number down one by one from 10 to 0.
Also when pressed I want to show UIImageView: "MyRocketWith", and when release the pressed touch I want to show UIImageView: "MyRocket".
class ViewController: UIViewController {
#IBOutlet weak var MyRocket: UIImageView!
#IBOutlet weak var MyRocketWith: UIImageView!
#IBOutlet weak var Countdown: UILabel!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: UITouch? = touches.first
if touch?.view != self.MyRocket {
self.MyRocketWith.isHidden = true
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
The below code shows and hides the UIImageView on click of UILabel
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var MyRocketWith: UIImageView!
#IBOutlet weak var CountDownLabel: UILabel!
#IBOutlet weak var MyRocket: UIImageView!
var count = 10
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.tapFunction))
CountDownLabel.isUserInteractionEnabled = true
CountDownLabel.addGestureRecognizer(tap)
MyRocket.isHidden = false
MyRocketWith.isHidden = true
MyRocketWith.tag=0
}
#IBAction func tapFunction(sender: UITapGestureRecognizer) {
print("tap working")
count = count-1
CountDownLabel.text = String(count)
if MyRocketWith.tag == 0
{
MyRocket.isHidden = true
MyRocketWith.isHidden = false
MyRocketWith.tag=1
}
else
{
MyRocket.isHidden = false
MyRocketWith.isHidden = true
MyRocketWith.tag=0
}
}
}

How to set NSButton.isEnabled from subclassed NSTextField

I'm very new to Swift MacOS programming and have been learning by writing small test applications.
The aim of this application is to enable the pushbutton when the 2nd textfield has the focus, and disable it when it is not focused.
I have found that by subclassing the NSTextField I can override becomeFirstResponder() however don't know how to set the button to be disabled from the subclass.
ViewController:
class ViewController: NSViewController {
#IBOutlet public weak var pushButton: NSButton!
#IBOutlet weak var textField3: NSTextField!
#IBOutlet weak var textField2: GSTextField!
#IBOutlet weak var textField1: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
textField2.delegate = self
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func chgButton(onoff: Bool){
pushButton.isEnabled = onoff
}
}
// When the field completes editing make the pushbutton disabled.
extension ViewController: NSTextFieldDelegate {
override func controlTextDidEndEditing(_ obj: Notification) {
print("did end")
chgButton(onoff: false)
}
}
GSTextField.Swift
class GSTextField: NSTextField {
override func becomeFirstResponder() -> Bool {
print("GSTextField Firstresponder")
////*** I need to set the button to be enabled here
return super.becomeFirstResponder()
}
}
Your NSTextField subclass needs to be able to communicate with the pushButton. The easiest way to do this is to pass a reference to the pushButton to your text field and then update the push button from there.
Update your ViewController like this:
override func viewDidLoad() {
super.viewDidLoad()
textField2.delegate = self
textField2.pushButton = pushButton
// Do any additional setup after loading the view.
}
And your GSTextField like this:
class GSTextField: NSTextField {
weak var pushButton: NSButton?
override func becomeFirstResponder() -> Bool {
print("GSTextField Firstresponder")
pushButton?.isEnabled = true
return super.becomeFirstResponder()
}
override func resignFirstResponder() -> Bool {
pushButton?.isEnabled = false
return super.resignFirstResponder()
}
}
It should be noted that while this works fine in this toy example, this is a sub-optimal solution to this problem because it tightly couples the pushButton and the GSTextField. A better solution would be to use delegation to communicate the focus changes to the ViewController, and let the ViewController handle the updates.
Your GSTextField would look like this:
protocol FocusObservable: class {
func didGainFocus(sender: Any)
func didLoseFocus(sender: Any)
}
class GSTextField: NSTextField {
weak var focusDelegate: FocusObservable?
override func becomeFirstResponder() -> Bool {
print("GSTextField Firstresponder")
focusDelegate?.didGainFocus(sender: self)
return super.becomeFirstResponder()
}
override func resignFirstResponder() -> Bool {
focusDelegate?.didLoseFocus(sender: self)
return super.resignFirstResponder()
}
}
And then you would add protocol conformance to the ViewController:
extension ViewController: FocusObservable {
func didGainFocus(sender: Any) {
pushButton.isEnabled = true
}
func didLoseFocus(sender: Any) {
pushButton.isEnabled = false
}
}
and set the focusDelegate of the text field:
override func viewDidLoad() {
super.viewDidLoad()
textField2.delegate = self
textField2.focusDelegate = self
// Do any additional setup after loading the view.
}

touchesBegan not responding in my code

//
// CommentViewController.swift
// Bordy
//
// Created by Micheal Tyler on 9/22/17.
// Copyright © 2017 Bordy,LLC. All rights reserved.
//
import UIKit
import FirebaseDatabase
import FirebaseAuth
class CommentViewController: UIViewController {
#IBOutlet weak var commentTextField: UITextField!
#IBOutlet weak var sendButton: UIButton!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var constraintToBottom: NSLayoutConstraint!
let postId = "-KuXb-8C5kijYNqOOihB"
var comments = [Comment]()
var users = [Users]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.estimatedRowHeight = 77
tableView.rowHeight = UITableViewAutomaticDimension
empty()
handleTextField()
loadComments()
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow(_:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide(_:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
/* This is where I have the code placed, when I tap on the screen in the simulator nothing is printed, my keyboard also doesnt hide if a user clicks on the screen to exit the textField. */
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
print("12121")
}
Since you're using a table view here, it's very likely that your table view's scroll view absorbs the touches before your view can get them.
If you want to hide the keyboard whenever the user taps on your table view, you may consider doing this with a UITapGestureRecognizer instead :
// MARK: LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
tableView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleHideKeyboard)))
}
// MARK: User Interaction
func handleHideKeyboard() {
commentTextField.resignFirstResponder()
}
Note that you can use resignFirstResponder() method to hide your keyboard:
resignFirstResponder()
Notifies this object that it has been asked to relinquish its status
as first responder in its window.

Overriding methods in a class extension constrained to a protocol in swift

I am trying to add default implementations to UIViewController touches began to all controllers conforming to a protocol through a protocol extension. There the touch would be sent to a custom view all controllers implementing this protocol have.
Here's the initial state:
protocol WithView {
var insideView: UIView! { get }
}
class Controller1: UIViewController, WithView {
var insideView: UIView!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
insideView.touchesBegan(touches, with: event)
}
/* Functionality of Controller 1 */
}
class Controller2: UIViewController, WithView {
var insideView: UIView!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
insideView.touchesBegan(touches, with: event)
}
/* Functionality of Controller 2 */
}
What I'd like to accomplish is a situation where all the UIViewControllers forwarded the touches to the insideView without specifying so for every controller the same way. Something like this:
protocol WithView {
var insideView: UIView! { get }
}
extension UIViewController where Self: WithView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
insideView.touchesBegan(touches, with: event)
}
}
class Controller1: UIViewController, WithView {
var insideView: UIView!
/* Functionality of Controller 1 */
}
class Controller2: UIViewController, WithView {
var insideView: UIView!
/* Functionality of Controller 2 */
}
But this does not compile, saying 'Trailing where clause for extension of non-generic type UIViewController'
I tried to define it the other way around, like so:
extension WithView where Self: UIViewController {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
insideView.touchesBegan(touches, with: event)
}
}
and while the extension is properly formatted, the compiler complains, as it cannot 'override' things in a protocol extension.
What I'd like is a class extension constrained to a protocol, such as I can override this methods and not being forced to copy-paste code inside all my controllers implementing this protocol.
Edit: as per proposed solutions
I also came up with this solution:
protocol WithView {
var insideView: UIView! { get }
}
extension UIViewController {
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let viewSelf = (self as? WithView) else {
super.touchesBegan(touches, with: event)
return
}
viewSelf.insideView.touchesBegan(touches, with: event)
}
}
class Controller1: UIViewController, WithView {
var insideView: UIView!
/* Functionality of Controller 1 */
}
class Controller2: UIViewController, WithView {
var insideView: UIView!
/* Functionality of Controller 2 */
}
It does what I want, but it feels a bit messy though, because then all the UIViewControllers would intherit this behavior, and would override its code, checking if they implement the protocol.
You can define your own superclass for all view controllers and check if self conforms to the particular protocol (WithView in your case) to decide if you should forward touch events to any other view.
class MyViewController: UIViewController {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let selfWithView = self as? WithView {
selfWithView.insideView.touchesBegan(touches, with: event)
} else {
super.touchesBegan(touches, with: event)
}
}
}
This is more flexible approach, you don't have to store insideView property in every view controller subclass.
You could do this by creating a class and sub-classing from it:
class WithViewController: UIViewController, WithView {
var insideView: UIView!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
insideView.touchesBegan(touches, with: event)
}
}
class ViewController: WithViewController {
}
The only downside to this is you have to have a default insideView and it never get's changed.

onClick Action with Label does not work [duplicate]

I would like to make a UILabel clickable.
I have tried this, but it doesn't work:
class DetailViewController: UIViewController {
#IBOutlet weak var tripDetails: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
...
let tap = UITapGestureRecognizer(target: self, action: Selector("tapFunction:"))
tripDetails.addGestureRecognizer(tap)
}
func tapFunction(sender:UITapGestureRecognizer) {
print("tap working")
}
}
Have you tried to set isUserInteractionEnabled to true on the tripDetails label? This should work.
Swift 3 Update
Replace
Selector("tapFunction:")
with
#selector(DetailViewController.tapFunction)
Example:
class DetailViewController: UIViewController {
#IBOutlet weak var tripDetails: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
...
let tap = UITapGestureRecognizer(target: self, action: #selector(DetailViewController.tapFunction))
tripDetails.isUserInteractionEnabled = true
tripDetails.addGestureRecognizer(tap)
}
#objc
func tapFunction(sender:UITapGestureRecognizer) {
print("tap working")
}
}
SWIFT 4 Update
#IBOutlet weak var tripDetails: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(GameViewController.tapFunction))
tripDetails.isUserInteractionEnabled = true
tripDetails.addGestureRecognizer(tap)
}
#objc func tapFunction(sender:UITapGestureRecognizer) {
print("tap working")
}
Swift 5
Similar to #liorco, but need to replace #objc with #IBAction.
class DetailViewController: UIViewController {
#IBOutlet weak var tripDetails: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
...
let tap = UITapGestureRecognizer(target: self, action: #selector(DetailViewController.tapFunction))
tripDetails.isUserInteractionEnabled = true
tripDetails.addGestureRecognizer(tap)
}
#IBAction func tapFunction(sender: UITapGestureRecognizer) {
print("tap working")
}
}
This is working on Xcode 10.2.
Swift 3 Update
yourLabel.isUserInteractionEnabled = true
Good and convenient solution:
In your ViewController:
#IBOutlet weak var label: LabelButton!
override func viewDidLoad() {
super.viewDidLoad()
self.label.onClick = {
// TODO
}
}
You can place this in your ViewController or in another .swift file(e.g. CustomView.swift):
#IBDesignable class LabelButton: UILabel {
var onClick: () -> Void = {}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
onClick()
}
}
In Storyboard select Label and on right pane in "Identity Inspector" in field class select LabelButton.
Don't forget to enable in Label Attribute Inspector "User Interaction Enabled"
You need to enable the user interaction of that label.....
For e.g
yourLabel.userInteractionEnabled = true
For swift 3.0 You can also change gesture long press time duration
label.isUserInteractionEnabled = true
let longPress:UILongPressGestureRecognizer = UILongPressGestureRecognizer.init(target: self, action: #selector(userDragged(gesture:)))
longPress.minimumPressDuration = 0.2
label.addGestureRecognizer(longPress)
Pretty easy to overlook like I did, but don't forget to use UITapGestureRecognizer rather than UIGestureRecognizer.
Thanks researcher
Here's my solution for programmatic user interface using UIKit.
I've tried it only on Swift 5. And It worked.
Fun fact is you don't have to set isUserInteractionEnabled = true explicitly.
import UIKit
open class LabelButon: UILabel {
var onClick: () -> Void = {}
public override init(frame: CGRect) {
super.init(frame: frame)
isUserInteractionEnabled = true
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
public convenience init() {
self.init(frame: .zero)
}
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
onClick()
}
}
Uses:
override func viewDidLoad() {
super.viewDidLoad()
let label = LabelButton()
label.text = "Label"
label.onClick = {
// TODO
}
}
Don't forget to set constraints. Otherwise it won't appear on view.
On top of all of the other answers, this depends on where the label is, it might be behind some subviews. You might think you tap on the label but maybe click the top view. To solve this you can bring the label view to the front with the following line.
self.view.bringSubviewToFront(lblView)
As described in the above solution
you should enable the user interaction first and add the tap gesture
this code has been tested using
Swift4 - Xcode 9.2
yourlabel.isUserInteractionEnabled = true
yourlabel.addGestureRecognizer(UITapGestureRecognizer(){
//TODO
})