I have created a subclass of UILabel, based on an example from here: UILabel doesn't show inputView. I am trying to create an instance of the label inside a class that subclasses UITableViewCell. The issue I am having is that to create an instance of DatePickerLabel it requires an NSCoder.
let dp = DatePickerLabel(coder: NSCoder)
I have this in my class that subclasses UITableViewCell but it doesn't seem to ever be triggered, leading to a null pointer when I run it (I tried using a variable and then assigning it inside this code):
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
print("triggered")
}
Any help is greatly appreciated, DatePickerLabel shown below!
class DatePickerLabel: UILabel {
private let _inputView: UIView? = {
let picker = UIDatePicker()
return picker
}()
private let _inputAccessoryToolbar: UIToolbar = {
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.isTranslucent = true
toolBar.sizeToFit()
return toolBar
}()
override var inputView: UIView? {
return _inputView
}
override var inputAccessoryView: UIView? {
return _inputAccessoryToolbar
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.plain, target: self, action: #selector(doneClick))
let spaceButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
_inputAccessoryToolbar.setItems([ spaceButton, doneButton], animated: false)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(launchPicker))
self.addGestureRecognizer(tapRecognizer)
}
override var canBecomeFirstResponder: Bool {
return true
}
#objc private func launchPicker() {
becomeFirstResponder()
}
#objc private func doneClick() {
resignFirstResponder()
}
}
Cheers!
In the question you linked to, the initialisation function was initWithCoder because that it used to load views from a storyboard or nib. You are not doing that.
So, change your init to a “plain” one, like:
init() {
super.init()
...
}
Related
I want to pass text of textField in custom class to ViewController and populate it to array when BarButtonItem of DatePicker is tapped. I used a callback as completion handler, but it caught EXC-BAD-ACCESS. What made this error and how could I pass text to my ViewController?
Custom class of textField
class HourDatePicker: UITextField {
var datePicker = UIDatePicker()
override init(frame: CGRect) {
super.init(frame: frame)
commominit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commominit()
}
func commominit(){
text = ""
datePicker.datePickerMode = .dateAndTime
datePicker.minuteInterval = 30
datePicker.locale = Locale(identifier: "ja")
datePicker.addTarget(self, action: #selector(setText), for: .valueChanged)
setText()
inputView = datePicker
inputAccessoryView = customPicker()
}
#objc func setText(){
let f = DateFormatter()
f.dateStyle = .full
f.timeStyle = .short
f.locale = Locale(identifier: "ja")
textColor = .black
text = "\(f.string(from: datePicker.date))"
}
private func customPicker() -> UIToolbar {
let toolbar = UIToolbar()
toolbar.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: 40)
let space = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: self, action: nil)
space.width = 100
let flexSpaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
let todayButtonItem = UIBarButtonItem(title: "today", style: .done, target: self, action: #selector(setToday))
let selectButtonItem = UIBarButtonItem(title: "select", style: .done, target: self, action: #selector(tellCalenderText))
let toolbarItem = [space, flexSpaceItem, todayButtonItem, selectButtonItem]
toolbar.setItems(toolbarItem, animated: true)
}
return toolbar
}
#objc func tellCalenderText(completion: ((_ titleText: String) -> Void)){
//I want to pass text here.
if text != "" {
guard let titleText = text else {return}
completion(titleText)
} else {
return
}
}
ViewController
class Calender1ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private let selectDate = HourDatePicker()
private var keepDate: [String] = []
#IBOutlet weak var timeTextView: UITextView!
#IBOutlet weak var dateText: HourDatePicker!
#IBOutlet weak var calenderTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.calenderTableView.delegate = self
self.calenderTableView.dataSource = self
selectDate.tellCalenderText {[weak self] (titleText) in
self?.bringDate(title: titleText)
}
}
func bringDate(title: String){
print("title: \(title)")
self.keepDate.append(title)
timeTextView.text.append(contentsOf: "\(title)\n")
}
Thank you.
This is error log.
error log
First, I think you're getting a bit messed up by having:
private let selectDate = HourDatePicker()
and having:
#IBOutlet weak var dateText: HourDatePicker!
and then making use of selectDate inside viewDidLoad()...
Give this a try. I only made a few changes, and tried to include enough comments to make it clear. I think you'll find this a much more straight-forward way of getting your custom HourDatePicker class to pass information back to the view controller:
class HourDatePicker: UITextField {
var datePicker = UIDatePicker()
override init(frame: CGRect) {
super.init(frame: frame)
commominit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commominit()
}
func commominit(){
text = ""
datePicker.datePickerMode = .dateAndTime
datePicker.minuteInterval = 30
datePicker.locale = Locale(identifier: "ja")
datePicker.addTarget(self, action: #selector(setText), for: .valueChanged)
setText()
inputView = datePicker
inputAccessoryView = customPicker()
}
#objc func setToday(){
datePicker.date = Date()
}
#objc func setText(){
let f = DateFormatter()
f.dateStyle = .full
f.timeStyle = .short
f.locale = Locale(identifier: "ja")
textColor = .black
text = "\(f.string(from: datePicker.date))"
}
private func customPicker() -> UIToolbar {
let toolbar = UIToolbar()
toolbar.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: 40)
let space = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: self, action: nil)
space.width = 100
let flexSpaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
let todayButtonItem = UIBarButtonItem(title: "today", style: .done, target: self, action: #selector(setToday))
let selectButtonItem = UIBarButtonItem(title: "select", style: .done, target: self, action: #selector(tellCalenderText))
let toolbarItem = [space, flexSpaceItem, todayButtonItem, selectButtonItem]
toolbar.setItems(toolbarItem, animated: true)
return toolbar
}
// "callback" closure
var tellController: ((String) ->())?
// triggered by "select" bar button tap
#objc func tellCalenderText() -> Void {
// get text from self
guard let t = text else {
return
}
// execute the callback closure
tellController?(t)
}
// #objc func tellCalenderText(completion: ((_ titleText: String) -> Void)){
// //I want to pass text here.
// if text != "" {
// guard let titleText = text else {return}
// completion(titleText)
// } else {
// return
// }
//
// }
}
//ViewController
class Calender1ViewController: UIViewController {
// not needed
//private let selectDate = HourDatePicker()
private var keepDate: [String] = []
// UITextView connected via Storyboard
#IBOutlet weak var timeTextView: UITextView!
// UITextField set to HourDatePicker, connected via Storyboard
#IBOutlet weak var dateText: HourDatePicker!
#IBOutlet weak var calenderTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// set the callback closure for the HourDatePicker
dateText.tellController = { [weak self] (titleText) in
self?.bringDate(title: titleText)
}
// not needed
//selectDate.tellCalenderText {[weak self] (titleText) in
// self?.bringDate(title: titleText)
//}
}
func bringDate(title: String){
print("title: \(title)")
self.keepDate.append(title)
timeTextView.text.append(contentsOf: "\(title)\n")
}
}
I'm getting the following error when I try to use an instance of MySegmentControl in the code below. The error occurs right after the app is being launched.
Any idea what am I missing?
Fatal error: Use of unimplemented initializer 'init(frame:)' for class 'TestingSubclassing.MySegmentControl'
Subclass of UISegementedControl
import UIKit
class MySegmentControl: UISegmentedControl {
init(actionName: Selector) {
let discountItems = ["One" , "Two"]
super.init(items: discountItems)
self.selectedSegmentIndex = 0
self.layer.cornerRadius = 5.0
self.backgroundColor = UIColor.red
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.blue.cgColor
self.addTarget(self, action: actionName, for: .valueChanged)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
ViewController
import UIKit
class ViewController: UIViewController {
let segmentOne: MySegmentControl = {
let segment1 = MySegmentControl(actionName: #selector(segmentAction))
return segment1
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(segmentOne)
}
#objc func segmentAction (sender: UISegmentedControl) {
print("segmentAction")
}
}
You could call super.init(frame and insert the segments manually.
And you have to add a target parameter to the custom init(actionName method.
class MySegmentControl: UISegmentedControl {
init(actionName: Selector, target: Any?) {
super.init(frame: .zero)
insertSegment(withTitle: "Two", at: 0, animated: false)
insertSegment(withTitle: "One", at: 0, animated: false)
self.selectedSegmentIndex = 0
self.layer.cornerRadius = 5.0
self.backgroundColor = UIColor.red
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.blue.cgColor
self.addTarget(target, action: actionName, for: .valueChanged)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Problem
I have an application that has a user registration view. It has many UITextField, and many of these have a picker with a toolbar embedded to close the picker i.e:
myTextField.inputView = myPicker
myTextField.inputAccessoryView = myToolbar
Essentially I want to reuse these text fields in different parts of my application, so I thought of subclassing UITextField, something like PickerUITextField.
Attempt
I've tried something like this:
class PickerUITextField: UITextField {
let picker = UIPickerView()
let toolbar = UIToolbar()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
withToolbar()
self.inputView = self.picker
self.inputAccessoryView = self.toolbar
}
private func withToolbar() {
toolbar.barStyle = UIBarStyle.default
toolbar.isTranslucent = true
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .done,
target: self, action: #selector(removeToolBar))
toolbar.setItems([space, doneButton], animated: false)
toolbar.isUserInteractionEnabled = true
toolbar.sizeToFit()
}
#objc func removeToolBar() {
self.resignFirstResponder()
}
}
Question
However, how can I detect in the view controller that the user has pressed the "Done" button of my PickerUITextField? In other words:
class UserRegistrationViewController: UIViewController {
#IBOutlet weak var country: PickerUITextField!
// I want this to be triggered whenever the country picker closes
func didSelectCountry() {
print("User selected \(country.text!)")
}
}
Thank you for the help.
You can create a closure in PickerUITextField to perform done button action.
class PickerUITextField: UITextField {
let picker = UIPickerView()
let toolbar = UIToolbar()
var doneBtnAction:(() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
withToolbar()
self.inputView = self.picker
self.inputAccessoryView = self.toolbar
}
private func withToolbar() {
toolbar.barStyle = UIBarStyle.default
toolbar.isTranslucent = true
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .done,
target: self, action: #selector(removeToolBar))
toolbar.setItems([space, doneButton], animated: false)
toolbar.isUserInteractionEnabled = true
toolbar.sizeToFit()
}
#objc func removeToolBar() {
doneBtnAction?()
self.resignFirstResponder()
}
}
And in your view controller, you can assign a closure. It will be called when you tap the done button.
class UserRegistrationViewController: UIViewController {
#IBOutlet weak var country: PickerUITextField!
override func viewDidLoad() {
super.viewDidLoad()
country.doneBtnAction = { [weak self] in
print("User selected \(self?.country.text!)")
}
}
}
You can use a protocol/delegate:
protocol PickerUITextFieldDelegate: class {
func didSelectCountry()
}
class PickerUITextField: UITextField {
// UITextField already have a 'delegate' we need a different name
weak var pickerDelegate: PickerUITextFieldDelegate?
let picker = UIPickerView()
let toolbar = UIToolbar()
#objc func removeToolBar() {
self.resignFirstResponder()
self.pickerDelegate?.didSelectCountry()
}
}
// We need to implement the PickerUITextFieldDelegate delegate here:
class UserRegistrationViewController: UIViewController, PickerUITextFieldDelegate {
#IBOutlet weak var country: PickerUITextField!
// Don't forget to set the delegate!
override func viewDidLoad() {
super.viewDidLoad()
self.country.pickerDelegate = self
}
// This will now be triggered by the delegate
func didSelectCountry() {
print("User selected \(country.text!)")
}
}
I have a Controller with these items
In stack view I add one or many xib file as UiView dynamically, this is my xib file Code:
class PassengerInfoItem: UIView {
#IBOutlet weak var View_Content: UIView!
#IBOutlet weak var Label_AddPassenger: UILabel!
override init(frame: CGRect)
{
super.init(frame: frame)
self.commenInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commenInit()
}
func commenInit()
{
Bundle.main.loadNibNamed("PassengerInfoItem", owner: self, options: nil)
self.View_Content.frame = self.bounds
self.View_Content.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(self.View_Content)
}}
and this is my controller code:
for i in 1...(UserSearchModel.Passengers.AdultNumber)!
{
let passengerInfoItem = PassengerInfoItem()
passengerInfoItem.tag = i
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapGesture_PassengerInfo))
tapGestureRecognizer.delegate = self
passengerInfoItem.addGestureRecognizer(tapGestureRecognizer)
self.StackView_AddPassengerInfo.addArrangedSubview(passengerInfoItem)
}
In stack view all passengerInfoItem added successfully but when touch one of those nothing happen and my problem is UITapGestureRecognizer do not work correctly , I checked all isUserInteractionEnabled = true for back View, Scroll View, Card, Stack View but UITapGesture not work
Because you add another instance here
Bundle.main.loadNibNamed("PassengerInfoItem", owner: self, options: nil)
self.View_Content.frame = self.bounds
self.View_Content.autoresizingMask = [.flexibleHeight, .flexibleWidth]
addSubview(self.View_Content)
so the gesture is applied to the back-most view , you may need
class func getInstance() -> PassengerInfoItem {
let vv = Bundle.main.loadNibNamed("PassengerInfoItem", owner: self, options: nil)?.first! as! PassengerInfoItem
return vv
}
Then replace
let passengerInfoItem = PassengerInfoItem()
with
let passengerInfoItem = PassengerInfoItem.getInstance()
I just got finished going through the big nerd ranch iOS programming book and started my first 'project'. I'm trying to use a UIImageView as a button but my image is coming up as nil and I cannot figure out why. I'm new at this, so any help, or even just identifying any parts of this code that don't make sense is appreciated.
import UIKit
class ImageScreenViewController: UIViewController {
# IBOutlet var imageView: UIImageView!
// set up images
let picture1 = UIImage(named: "picture1")
override func viewDidLoad() {
super.viewDidLoad()
// set image on screen
imageView.image = picture1
}
// set up gesture recognizer
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageView.addGestureRecognizer(tapRecognizer)
self.imageView.isUserInteractionEnabled = true
}
#objc func count(_ gestureRecognizer: UIGestureRecognizer) {
print("tapped the picture")
}
}
note, picture1 is in Assets.xcassets.
Some explanation for your question
encodeWithCoder(_ aCoder: NSCoder) {
// Serialize
}
init(coder aDecoder: NSCoder) {
// Deserialize
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//You used
}
To add a Gesture you actually do not need to implement coder here as These are being used to store the state
Check following example
1- ImageView is being taken from storyBoard
2- myImage is being Added using code
Now , Storyboard initially implants serialise and deserialise mechanism
Thus imageView is a property being added from storyboard so no need for coder there
import UIKit
class ImageVC: UIViewController {
# IBOutlet var imageView: UIImageView!
// set up images
let picture1 = UIImage(named: "cc")
//ImageView programmaticlly
var myImage = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// set image on screen
imageView.image = picture1
//setting properties and frame
myImage.image = picture1
myImage.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
self.view.addSubview(myImage)
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageView.addGestureRecognizer(tapRecognizer)
self.imageView.isUserInteractionEnabled = true
}
// set up gesture recognizer
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//adding gesture
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.myImage.addGestureRecognizer(tapRecognizer)
self.myImage.isUserInteractionEnabled = true
}
#objc func count(_ gestureRecognizer: UIGestureRecognizer) {
print("tapped the picture")
}
}
As if you implement above Algo It won't crash , as For Properties being added from storyboard can directly be accessed in didload or willAppear as we never going to remove them from superview so indirectly its serialised
Second case - myImage - Added Programmatically can also be removed from superView and added again Thus can be serialised means need to be
you can add tapRecongnize in viewDidload because in init method imageview not initialize and return nil so that you can use like this
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = picture1
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageView.addGestureRecognizer(tapRecognizer)
self.imageView.isUserInteractionEnabled = true
}
var image: UIImage?
#IBOutlet weak var imageChoisie: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
imageChoisie.image = image // from Segue
// Click on Image
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(count(_:)))
self.imageChoisie.addGestureRecognizer(tapRecognizer)
self.imageChoisie.isUserInteractionEnabled = true
}
#objc func count(_ gestureRecognizer: UIGestureRecognizer) {
//print("tapped the picture")
dismiss(animated: true, completion: nil) // back previous screen
}