Class has no initializers when using POP on a variable - ios

I want to apply protocol oriented programming in one of my applications. I created a protocol called "CustomAnchor" to get rid of the huge amount of commands, needed for Autolayout. But when I assign this protocol to a constant e.g profileImage, the Controller gets the following error:
Class 'ProfileController' has no initializers
On the constant I get this gray warning:
Stored property 'profileImageView' without initial value prevents synthesized initializers
This is how the code looks like: (Use of POP at the bottom)
class ProfileController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.white
self.navigationItem.title = "Profil"
self.navigationItem.largeTitleDisplayMode = .never
fillData()
setupView()
confBounds()
}
func fillData() {
profileImageView.image = UIImage(named: "test")
}
func setupView() {
view.addSubview(profileImageView)
}
func confBounds() {
profileImageView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
profileImageView.anchor(top: self.view.topAnchor, left: nil, bottom: nil, right: nil, paddingTop: 60, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 140, height: 140)
}
let profileImageView: UIImageView, CustomAnchor = { () -> UIImageView in
let pView = UIImageView()
pView.contentMode = .scaleAspectFill
pView.clipsToBounds = true
pView.image = UIImage(named: "test")
return pView
}()
}

Because no one answered I made a simple workaround and gave the Property to the Controller. Not really what I wanted (smooth POP) but it works..

Related

Perform Segue is not working when i am using programmatic UI

I am creating an app for my personal project using programmaticUI and storyboard for the UI part, but i found an issue when i tried to performSegue from my "SecondViewController" to my "ThirdViewController" , i added the "identifier" in my segue like usual:
And then i called the "performSegue" from my SecondViewController:
import UIKit
class SecondViewController: UIViewController {
private var myItem = [SecondItem]()
lazy var myTableView : UITableView = {
let myTable = UITableView()
myTable.translatesAutoresizingMaskIntoConstraints = false
return myTable
}()
private let myContentView : UIView = {
let view = UIView()
view.backgroundColor = .gray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var label : UILabel = {
let myLabel = UILabel()
myLabel.text = "Hello"
return myLabel
}()
private let unameTextField : UITextField = {
let txtField = UITextField()
txtField.backgroundColor = .white
txtField.placeholder = "Username"
txtField.borderStyle = .roundedRect
txtField.translatesAutoresizingMaskIntoConstraints = false
return txtField
}()
private let pwordTxtField : UITextField = {
let txtField = UITextField()
txtField.placeholder = "Password"
txtField.borderStyle = .roundedRect
txtField.translatesAutoresizingMaskIntoConstraints = false
return txtField
}()
private let loginBtn : UIButton = {
let btn = UIButton(type: .system)
btn.backgroundColor = .blue
btn.setTitle("Login", for: .normal)
btn.tintColor = .white
btn.layer.cornerRadius = 5
btn.clipsToBounds = true
btn.translatesAutoresizingMaskIntoConstraints = false
btn.addTarget(self, action: #selector(btnPressed), for: .touchUpInside)
return btn
}()
//I called the "performSegue" here
#objc func btnPressed() {
performSegue(withIdentifier: "gotoBla", sender: self)
print("button pressed")
}
lazy var imageView : UIImageView = {
let image = UIImage(named: "image_4")
let imageView = UIImageView(image: image)
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
func setAutoLayout(){
let guide = view.safeAreaLayoutGuide
myContentView.anchor(top: guide.topAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: view.frame.height / 3, enableInsets: true)
imageView.anchor(top: myContentView.topAnchor, left: nil , bottom: nil , right: nil , paddingTop: 10, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 80, height: 80, enableInsets: true)
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
unameTextField.anchor(top: imageView.bottomAnchor, left: myContentView.leftAnchor, bottom: nil, right: myContentView.rightAnchor, paddingTop: 10, paddingLeft: 20, paddingBottom: 5, paddingRight: 20, width: 0, height: 40, enableInsets: true)
pwordTxtField.anchor(top: unameTextField.bottomAnchor, left: myContentView.leftAnchor, bottom: nil, right: myContentView.rightAnchor, paddingTop: 30, paddingLeft: 20, paddingBottom: 0, paddingRight: 20, width: 0, height: 40, enableInsets: true)
loginBtn.anchor(top: pwordTxtField.bottomAnchor, left: myContentView.leftAnchor, bottom: nil, right: myContentView.rightAnchor , paddingTop: 20, paddingLeft: 20, paddingBottom: 0, paddingRight: 20, width: 0, height: 40, enableInsets: true)
//TableView
myTableView.topAnchor.constraint(equalTo: myContentView.bottomAnchor).isActive = true
myTableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
myTableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
myTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
myItem.append(SecondItem(text: "first"))
myItem.append(SecondItem(text: "Second"))
myItem.append(SecondItem(text: "Third"))
view.backgroundColor = .white
view.addSubview(myContentView)
myContentView.addSubview(unameTextField)
myContentView.addSubview(pwordTxtField)
myContentView.addSubview(loginBtn)
myContentView.addSubview(imageView)
myTableView.register(SecondTableViewCell.self, forCellReuseIdentifier: K.SecondTableViewCell.identifier)
myTableView.delegate = self
myTableView.dataSource = self
view.addSubview(myTableView)
setAutoLayout()
}
}
extension SecondViewController : UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.row)
}
}
extension SecondViewController : UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
myItem.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: K.SecondTableViewCell.identifier, for: indexPath) as! SecondTableViewCell
cell.second = myItem[indexPath.row]
cell.selectionStyle = .none
return cell
}
}
And for the Third View Controller, i am not yet adding some code in there
import UIKit
class ThirdViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
But everytime i run the app and click the login button,it always gave me this error:
This is what my app looks like:
Do i miss something here? I am a beginner by the way, i hope you guys can help me. Thank you
When posting questions here, it would be good for you to take a few minutes to review How to Ask
However, based on the little bit of info you've provided...
Almost certainly the problem is that you are adding View Controllers in Storyboard and then improperly trying to use them via code.
For example, I'm guessing that you have code in your "first" view controller to load and display SecondViewController like this:
#objc func showSecondTapped(_ sender: Any) {
let vc = SecondViewController()
navigationController?.pushViewController(vc, animated: true)
}
and then in SecondViewController you're trying to use the Storyboard associated segue with this:
#objc func btnPressed(_ sender: Any) {
performSegue(withIdentifier: "gotoBla", sender: self)
print("button pressed")
}
However, that segue doesn't exist as part of SecondViewController code ... it is part of the Storyboard object.
Back in your first view controller, if you load and push to SecondViewController like this:
#objc func showSecondTapped(_ sender: Any) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "secondVC") as? SecondViewController {
navigationController?.pushViewController(vc, animated: true)
}
}
you will then be able to call performSegue because you loaded it from the Storyboard.
if let vc = storyboard?.instantiateViewController(withIdentifier: "secondVC") as? SecondViewController {
navigationController?.pushViewController(vc, animated: true)
// if navigationController?.pushViewController doesn't work you can try this
present(vc, animated: true) {
// anything you want to perform after presenting new screen
}
// and try this to show in full screen with different transition effect
vc.modalTransitionStyle = .crossDissolve
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true) {
// anything you want to perform after presenting new screen
}
}

How to segue smoothly from UITableViewController to UIViewController programmatically

I'm not using storyboard and I'm doing everything programmatically.
So my problem is that when try to run the app on my phone (This does not happen when I use the simulator in Xcode), and when I use the following method...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let destinationVC = ThirdViewController()
destinationVC.selectedExercise = exercises![indexPath.row]
self.navigationController?.pushViewController(destinationVC, animated: true)
}
...to transition from a UITableView to a UIView, the contents of the View transition normally, but the background freezes for a second before transitioning to the UIView.
What's causing this and how can I fix this?
Here is the code for the viewController that I'm transitioning to.
import UIKit
import RealmSwift
class ThirdViewController: UIViewController, UITextViewDelegate {
let realm = try! Realm()
var stats : Results<WeightSetsReps>?
var weightTextField = UITextField()
var weightLabel = UILabel()
var notesTextView = UITextView()
var repsTextField = UITextField()
var repsLabel = UILabel()
var timerImage = UIImageView()
var nextSet = UIButton()
var nextExcersise = UIButton()
var selectedExercise : Exercises? {
didSet{
loadWsr()
}
}
//MARK: - ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
notesTextView.delegate = self
timeClock()
navConAcc()
labelConfig()
setTextFieldConstraints()
setImageViewConstraints()
setTextViewConstraints()
setButtonConstraints()
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
//MARK: - UILabel
func labelConfig(){
weightTextField.placeholder = "Total weight..."
weightTextField.layer.borderWidth = 1
weightTextField.backgroundColor = .white
weightTextField.layer.cornerRadius = 25
weightTextField.layer.borderColor = UIColor.lightGray.cgColor
weightLabel.text = " Weight (lbs): "
weightLabel.textColor = .black
weightTextField.leftView = weightLabel
weightTextField.leftViewMode = .always
repsTextField.placeholder = "Number of Reps..."
repsTextField.layer.borderWidth = 1
repsTextField.backgroundColor = .white
repsTextField.layer.cornerRadius = 25
repsTextField.layer.borderColor = UIColor.lightGray.cgColor
repsLabel.text = " Repetitions: "
repsLabel.textColor = .black
notesTextView.layer.borderWidth = 1
notesTextView.backgroundColor = .white
notesTextView.layer.cornerRadius = 25
notesTextView.layer.borderColor = UIColor.lightGray.cgColor
notesTextView.text = " Notes..."
notesTextView.textColor = UIColor.lightGray
notesTextView.returnKeyType = .done
repsTextField.leftView = repsLabel
repsTextField.leftViewMode = .always
nextSet.layer.borderWidth = 1
nextSet.backgroundColor = .white
nextSet.layer.cornerRadius = 25
nextSet.layer.borderColor = UIColor.lightGray.cgColor
nextSet.setTitle("Next Set", for: .normal)
nextSet.setTitleColor(.black, for: .normal)
nextSet.addTarget(self, action: #selector(addNewSet), for: .touchUpInside)
nextExcersise.layer.borderWidth = 1
nextExcersise.backgroundColor = .white
nextExcersise.layer.cornerRadius = 25
nextExcersise.layer.borderColor = UIColor.lightGray.cgColor
nextExcersise.setTitle("Next Exercise", for: .normal)
nextExcersise.setTitleColor(.black, for: .normal)
[weightTextField, repsTextField, notesTextView, nextSet, nextExcersise].forEach{view.addSubview($0)}
}
//MARK: - TextView Delegates
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == " Notes..." {
textView.text = ""
textView.textColor = UIColor.black
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
textView.resignFirstResponder()
}
return true
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text == ""{
notesTextView.text = " Notes..."
notesTextView.layer.borderColor = UIColor.lightGray.cgColor
}
}
//MARK: - Dismiss Keyboard Function
#objc func dismissKeyboard(){
view.endEditing(true)
}
//MARK: - TextField Constraints
func setTextFieldConstraints(){
weightTextField.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor,padding: .init(top: 20, left: 40, bottom: 0, right: -40), size: .init(width: 0, height: 50))
repsTextField.anchor(top: weightTextField.bottomAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor, padding: .init(top: 30, left: 40, bottom: 0, right: -40) ,size: .init(width: 0, height: 50))
}
//MARK: - UIButton Functions
#objc func addNewSet(){
print("It Works")
}
//MARK: - UIButton Constraints
func setButtonConstraints(){
nextSet.anchor(top: nil, leading: view.leadingAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: nil, padding: .init(top: 0, left: 40, bottom: 0, right: -150), size: .init(width: 120, height: 70))
nextExcersise.anchor(top: nil, leading: nextSet.trailingAnchor, bottom: nextSet.bottomAnchor, trailing: view.trailingAnchor, padding: .init(top: 0, left: 85, bottom: 0, right: -40), size: .init(width: 120, height: 70))
}
//MARK: - ImageView Constraints
func setImageViewConstraints(){
timerImage.anchor(top: repsTextField.bottomAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor, padding: .init(top: 40, left: 0, bottom: 0, right: 0), size: .init(width: 100, height: 100))
}
//MARK: - TextView Constraints
func setTextViewConstraints(){
notesTextView.anchor(top: timerImage.bottomAnchor, leading: view.leadingAnchor, bottom: nil, trailing: view.trailingAnchor, padding: .init(top: 40, left: 40, bottom: 0, right: -40), size: .init(width: 100, height: 110))
}
//MARK: - Navigation Bar Setup
func navConAcc(){
navigationItem.title = selectedExercise?.exerciseName
navigationController?.navigationBar.prefersLargeTitles = true
}
//MARK: - Stopwatch
func timeClock(){
let image1 = UIImage(named: "stopwatch")
timerImage = UIImageView(image: image1)
timerImage.contentMode = .scaleAspectFit
self.view.addSubview(timerImage)
}
//MARK: - Load Data
func loadWsr() {
stats = selectedExercise?.wsr.sorted(byKeyPath: "sets", ascending: true)
}
//MARK: - Save Data
func save(wsr : WeightSetsReps){
do {
try realm.write {
realm.add(wsr)
}
} catch {
print("Error saving wsr data \(error)")
}
}
}
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero){
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true
}
if let leading = leading {
leadingAnchor.constraint(equalTo: leading, constant: padding.left).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: padding.bottom).isActive = true
}
if let trailing = trailing {
trailingAnchor.constraint(equalTo: trailing, constant: padding.right).isActive = true
}
if size.width != 0 {
widthAnchor.constraint(equalToConstant: size.width).isActive = true
}
if size.height != 0 {
heightAnchor.constraint(equalToConstant: size.height).isActive = true
}
}
}
You're not implementing programmatic view controllers correctly. A programmatically-created view controller does all of its view building in loadView(), not viewDidLoad(). Therefore, add all of the view controller's subviews in loadView() (without calling super.loadView()). Then use viewDidLoad() (with calling super.viewDidLoad()) to do post-view work, like adding timers, notification observers, etc. I suspect the lagging is caused by an incorrectly-configured lifecycle.
It also appears you're relatively new to iOS or Swift development and so I would strongly suggest you do not use extensions, especially on UIView for auto layout. Learn how it all works first before you begin extending things. The process for programmatic auto layout is:
// adjust parameters first, like color, delegate, etc.
someView.translatesAutoresizingMaskIntoConstraints = false // set resizing to false before adding as a subview
view.addSubview(someView) // add as a subview
someView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true // add constraints

How to prefetch data from firebase database then present the view with those datas in Textfield

I'm trying to prefetch username from firebase database then show it in a textfield when the view is presented with no delay.
Problem is I can't seem to prefetch data from the database before the view is presented.
I tried having an if statement inside the observe single view that says if self.username != nil then configure the view (Moved the view did load configure view functions inside this if statement). This almost did what I wanted but it showed the animation of the view loading and waited a couple of seconds till it got the textfield loaded with it data. Trying to eliminate the delay and make it instantly load with the view.
Also tried to add func setData { userTextField.text = username } and call it in getData in observe, still 2 second delay.
Note:
ProfileController file self.present AccountInfoController File
Extension File:
extension UIView {
func labelTextContainerView(view: UIView, label: UILabel,_ textField: UITextField) -> UIView {
view.backgroundColor = .white
view.addSubview(label)
label.font = UIFont.systemFont(ofSize: 10)
label.textColor = UIColor.mainBlue()
label.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: nil, right: nil, paddingTop: 10, paddingLeft: 20, paddingBottom: 0, paddingRight: 0, width: 0, height: 9)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
if (textField.text?.isEmpty ?? true) {
label.isHidden = true
}
view.addSubview(textField)
//textField.textColor = .black
textField.anchor(top: label.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 20, paddingBottom: 0, paddingRight: 20, width: 0, height: 0)
textField.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let separatorView = UIView()
separatorView.backgroundColor = UIColor(white: 0.87, alpha: 1)
view.addSubview(separatorView)
separatorView.anchor(top: nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 20, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.75)
return view
}
}
extension UITextField {
func editableTextField(withPlaceolder placeholder: String, someText: String?, enableEditing: Bool) -> UITextField {
let tf = UITextField()
tf.borderStyle = .none
tf.font = UIFont.systemFont(ofSize: 16)
if (enableEditing == false) {
tf.isEnabled = enableEditing
tf.isUserInteractionEnabled = enableEditing
tf.alpha = 0.5
}
tf.text = someText
tf.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [NSAttributedString.Key.foregroundColor: UIColor(white: 0.8, alpha: 1)])
return tf
}
}
AccountInfoController FILE :
import UIKit
import Firebase
class AccountInfoController: UIViewController {
//MARK: - Properties
var ref = Database.database().reference()
var userID = Auth.auth().currentUser?.uid
var username: String?
var userLabel = UILabel()
lazy var userContainerView: UIView = {
let view = UIView()
return view.labelTextContainerView(view: view, label: userLabel, userTextField)
}()
lazy var userTextField: UITextField = {
userLabel.text = "Username"
userLabel.isHidden = false
let tf = UITextField()
return tf.editableTextField(withPlaceolder: "Username", someText: self.username?.lowercased(), enableEditing: false)
}()
//MARK: - Init
override func viewDidLoad() {
super.viewDidLoad()
getUserData()
//print(self.username)
configureViewComponents()
configureViewLabel()
}
func getUserData() {
user.child("username").observeSingleEvent(of: .value) { (snapshot) in
guard let userName = snapshot.value as? String else { return }
print(userName)
self.username = userName
}
}
// MARK: - Helper Functions
func configureViewLabel() {
//some code
}
func configureViewComponents() {
//some code
view.addSubview(userContainerView)
userContainerView.anchor(top: emailContainerView.bottomAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 60)
//some code
}
}
Expectation: When presenting AccountInfoController I want the textfield to have the username of the current user without waiting for database to fetch the data, I expect the database to prefetch the username.
Current Results: The code above doesn't even show the username in the textfield after database fetches it. unless the if statement mentioned above is added therefore there is a 2 seconds delay.
I believe I found an answer to my problem.
All that needed to be done is declaring a public variable for all classes to use, then at the Home page where your app first load after signing in fetch the data from the database and assign them to the public variables.
This solved my issue.
In profile controller file:
Outside the class:
public var username: String?
inside the class:
I moved the getUserData() func from AccountInfoController to ProfileController
inside the viewDidLoad():
getUserData()
Finally when initializing the text field I assigned the text to be the public variable.

Uncaught Exception in Button

I have a uiview with a couple of elements that I use for a comment function in my app. There is a text field, button, and line separator. Everything renders fine however when I click submit the app crashes and I get this error.
'NSInvalidArgumentException', reason: '-[UIButton copyWithZone:]: unrecognized selector sent to instance 0x7fe58c459620'
I don't see anything wrong with my implementation so this error is a little confusing to me. This is the class for my UIView
import UIKit
protocol CommentInputAccessoryViewDelegate {
func handleSubmit(for comment: String?)
}
class CommentInputAccessoryView: UIView, UITextFieldDelegate {
var delegate: CommentInputAccessoryViewDelegate?
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
fileprivate let submitButton: UIButton = {
let submitButton = UIButton(type: .system)
submitButton.setTitle("Submit", for: .normal)
submitButton.setTitleColor(.black, for: .normal)
submitButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
submitButton.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
//submitButton.isEnabled = false
return submitButton
}()
lazy var commentTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Add a comment"
textField.delegate = self
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
return textField
}()
override init(frame: CGRect) {
super.init(frame: frame)
// backgroundColor = .red
addSubview(submitButton)
submitButton.anchor(top: topAnchor, left: nil, bottom: bottomAnchor, right:rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 50, height: 0)
addSubview(commentTextField)
commentTextField.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: submitButton.leftAnchor, paddingTop: 0, paddingLeft: 12, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
setupLineSeparatorView()
}
fileprivate func setupLineSeparatorView(){
let lineSeparatorView = UIView()
lineSeparatorView.backgroundColor = UIColor.rgb(red: 230, green: 230, blue: 230)
addSubview(lineSeparatorView)
lineSeparatorView.anchor(top:topAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
}
#objc func handleSubmit(for comment: String?){
guard let commentText = commentTextField.text else{
return
}
delegate?.handleSubmit(for: commentText)
}
#objc func textFieldDidChange(_ textField: UITextField) {
let isCommentValid = commentTextField.text?.count ?? 0 > 0
if isCommentValid {
submitButton.isEnabled = true
}else{
submitButton.isEnabled = false
}
}
func clearCommentTextField(){
commentTextField.text = nil
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is the accompanying class that ultimately handles the submission through a protocol method
//allows you to gain access to the input accessory view that each view controller has for inputting text
lazy var containerView: CommentInputAccessoryView = {
let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 50)
let commentInputAccessoryView = CommentInputAccessoryView(frame:frame)
commentInputAccessoryView.delegate = self
return commentInputAccessoryView
}()
#objc func handleSubmit(for comment: String?){
guard let comment = comment, comment.count > 0 else{
return
}
let userText = Comments(content: comment, uid: User.current.uid, profilePic: User.current.profilePic!,eventKey: eventKey)
sendMessage(userText)
// will clear the comment text field
self.containerView.clearCommentTextField()
}
extension NewCommentsViewController {
func sendMessage(_ message: Comments) {
ChatService.sendMessage(message, eventKey: eventKey)
}
}
The associated method for the target/action #selector(handleSubmit) must be
#objc func handleSubmit(_ sender: UIButton) { ...
or
#objc func handleSubmit() { ...
Other forms are not supported.
Does the code compile at all?
Actually you can't use self in the initializer let submitButton: UIButton = { .. }()
The problem seems to be that UIButton doesn't have a copyWithZone method and that you can't define delegates for UIButtons:
what are the delegate methods available with uibutton

How to navigate another view controller when clicking on item inside cell of collectionView Using Swift 3

We want to show another controller by replacing main controller when clicking on item (i.e ImageView) inside a cell collection. But we are not able to get main collection view and can't able to navigate targeted controller. We are using following approach -
HomeViewComtroller.swift
import LBTAComponents
class HomeViewController: DatasourceController {
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Home"
collectionView?.contentInset = UIEdgeInsetsMake(50, 0, 0, 0)
collectionView?.scrollIndicatorInsets = UIEdgeInsetsMake(50, 0, 0, 0)
collectionView?.backgroundColor = UIColor(r: 232, g: 236, b: 241, a: 1)
let homeViewDatasource = HomeViewDatasource()
self.datasource = homeViewDatasource
}
override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.section == 0 {
return CGSize(width: view.frame.width, height: 120)
} else if indexPath.section == 6 {
return CGSize(width: view.frame.width, height: 120)
} else {
return CGSize(width: view.frame.width, height: 200)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 33)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 10)
}
// lazy var homeMainCatgCell: HomeMainCatgCell = {
// let homemain = HomeMainCatgCell ()
// homemain.homeViewcontroller = self
// return homemain
// }()
func handleCtgClick (ctgname: String ,ctgId: String) {
let dummySettingViewController = UIViewController()
dummySettingViewController.view.backgroundColor = UIColor.white
dummySettingViewController.navigationItem.title = ctgname
navigationController?.navigationBar.tintColor = UIColor.white
navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
navigationController?.pushViewController(dummySettingViewController, animated: true)
}
}
HomeViewDatasource
import LBTAComponents
class HomeViewDatasource: Datasource {
override func headerClasses() -> [DatasourceCell.Type]? {
return [HomeMainCatgCellHeader.self,HomeSaleCatgCellHeader.self,HomeNewArrivalCellHeader.self,HomeBestSallingCellHeader.self,HomeBigDiscountCellHeader.self,HomeSpecialOfferCellHeader.self,HomeBrandCellHeader.self]
}
override func footerClasses() -> [DatasourceCell.Type]? {
return[HomeCellsFooter.self]
}
override func cellClasses() -> [DatasourceCell.Type] {
return [HomeMainCatgCell.self,HomeSaleCtagCell.self,HomeNewArrivalsCell.self,HomeBestSallingCatgCell.self,HomeBigDiscountCatgCell.self,HomeSpecialOfferCatgCell.self,HomeBrandVIewCell.self]
}
override func numberOfItems(_ section: Int) -> Int {
return 1
}
override func numberOfSections() -> Int {
return 7
}
}
HomeMainCatgCell.swift
import LBTAComponents
class HomeMainCatgCell: DatasourceCell {
var homeViewcontroller: HomeViewController?
let personalcareCatgImageView: UIImageView = {
let iv = UIImageView()
iv.image = #imageLiteral(resourceName: "personalcareimage")
iv.tag = 0
iv.contentMode = .scaleToFill
return iv
}()
let healthcareCatgImageView: UIImageView = {
let iv = UIImageView()
iv.image = #imageLiteral(resourceName: "healthcareimage")
iv.contentMode = .scaleToFill
iv.tag = 1
return iv
}()
let homecareCatgImageView: UIImageView = {
let iv = UIImageView()
iv.image = #imageLiteral(resourceName: "homecareimage")
iv.contentMode = .scaleToFill
iv.tag = 2
return iv
}()
let kitchencareCatgImageView: UIImageView = {
let iv = UIImageView()
iv.image = #imageLiteral(resourceName: "kitchencareiamge")
iv.contentMode = .scaleToFill
iv.tag = 3
return iv
}()
override func setupViews() {
super.setupViews()
let personalcareCatgImageContainerView = UIView()
personalcareCatgImageContainerView.backgroundColor = .white
let homecareCatgImageContainerView = UIView()
homecareCatgImageContainerView.backgroundColor = .white
let healthcareCatgImageContainerView = UIView()
healthcareCatgImageContainerView.backgroundColor = .white
let kitchencareCatgImageContainerView = UIView()
kitchencareCatgImageContainerView.backgroundColor = .white
let imageStackView = UIStackView(arrangedSubviews: [personalcareCatgImageContainerView,homecareCatgImageContainerView,healthcareCatgImageContainerView,kitchencareCatgImageContainerView])
imageStackView.axis = .horizontal
imageStackView.distribution = .fillEqually
addSubview(imageStackView)
imageStackView.anchor(topAnchor, left: leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: frame.width, heightConstant: frame.height)
imageStackView.addSubview(personalcareCatgImageView)
imageStackView.addSubview(healthcareCatgImageView)
imageStackView.addSubview(homecareCatgImageView)
imageStackView.addSubview(kitchencareCatgImageView)
personalcareCatgImageView.anchor(personalcareCatgImageContainerView.topAnchor, left: personalcareCatgImageContainerView.leftAnchor, bottom: personalcareCatgImageContainerView.bottomAnchor, right: personalcareCatgImageContainerView.rightAnchor, topConstant: 5, leftConstant: 5, bottomConstant: 5, rightConstant: 5, widthConstant: personalcareCatgImageContainerView.frame.width , heightConstant: personalcareCatgImageContainerView.frame.width)
healthcareCatgImageView.anchor(healthcareCatgImageContainerView.topAnchor, left: healthcareCatgImageContainerView.leftAnchor, bottom: healthcareCatgImageContainerView.bottomAnchor, right: healthcareCatgImageContainerView.rightAnchor, topConstant: 5, leftConstant: 5, bottomConstant: 5, rightConstant: 5, widthConstant: healthcareCatgImageContainerView.frame.width , heightConstant: healthcareCatgImageContainerView.frame.width)
homecareCatgImageView.anchor(homecareCatgImageContainerView.topAnchor, left: homecareCatgImageContainerView.leftAnchor, bottom: homecareCatgImageContainerView.bottomAnchor, right: homecareCatgImageContainerView.rightAnchor, topConstant: 5, leftConstant: 5, bottomConstant: 5, rightConstant: 5, widthConstant: homecareCatgImageContainerView.frame.width , heightConstant: homecareCatgImageContainerView.frame.width)
kitchencareCatgImageView.anchor(kitchencareCatgImageContainerView.topAnchor, left: kitchencareCatgImageContainerView.leftAnchor, bottom: kitchencareCatgImageContainerView.bottomAnchor, right: kitchencareCatgImageContainerView.rightAnchor, topConstant: 5, leftConstant: 5, bottomConstant: 5, rightConstant: 5, widthConstant: kitchencareCatgImageContainerView.frame.width , heightConstant: kitchencareCatgImageContainerView.frame.width)
personalcareCatgImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClickCtg)))
personalcareCatgImageView.isUserInteractionEnabled = true
healthcareCatgImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClickCtg)))
healthcareCatgImageView.isUserInteractionEnabled = true
homecareCatgImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClickCtg)))
homecareCatgImageView.isUserInteractionEnabled = true
kitchencareCatgImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClickCtg)))
kitchencareCatgImageView.isUserInteractionEnabled = true
}
func handleClickCtg(gestureRecognizer: UITapGestureRecognizer) {
var CtgName: String? = nil
var CtgId: String? = nil
guard let tag = gestureRecognizer.view?.tag else {return}
print(tag)
switch tag {
case 0:
CtgName = "Personal Care"
CtgId = "121"
case 1:
CtgName = "Health Care"
CtgId = "122"
case 2:
CtgName = "Home Care"
CtgId = "123"
case 3:
CtgName = "Kitchen Care"
CtgId = "124"
default:
return
}
self.homeViewController?.handleCtgClick(ctgname: CtgName! ,ctgId: CtgId!)
}
}
Main Problem
first of all i am not binding the click of entire cell, I only want to bing click on ImageView inside cell. So for that i bind GestureRecognizer on image view like -
kitchencareCatgImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleClickCtg)))
Click event works fine in terms of it's print the tag (added on image view) via print(tag) inside click function. But when call function self.homeViewcontroller.handleCtgClick(ctgname: CtgName! ,ctgId: CtgId!) (function exist in HomeViewcontroller class). it's not changing the view controller.
I also debug handleCtgClick function and i found i am getting the object of HomeViewcontroller is nil.
thanks in Advance
If you declare a global instance of your controller in your cell class this will create a Reference cycle and create a leak in your app. Your cell will have a strong reference to your controller and your controller will have a strong reference to your tableview which in turn keeps a strong reference to the cell, and second you might not be able to tell the index path of the clicked cell if you allow editing. You can try the following method to overcome both problems.
First of all create an enum in your cell class with cases as named according to your image views so your controller knows which image view is clicked and you can link your tags to it too. Something like:
enum CategoryViews:Int{
case personal = 0,healthcare = 1,homecare = 2,kitchencare = 3
}
Then create a protocol in your cell class like:
protocol HomeMainCatgCellDelegate:class{
func clicked(view:CategoryViews,forCell cell:HomeMainCatgCell)
}
Then create a weak variable in your cell class and call the protocol method in your action handler method
class HomeMainCatgCell: DatasourceCell {
weak var delegate:HomeMainCatgCellDelegate?
//Remove the variable var homeViewcontroller: HomeViewController? to avoid reference cycle and we don't need it anyway.
...
func handleClickCtg(gestureRecognizer: UITapGestureRecognizer) {
guard let tag = gestureRecognizer.view?.tag else {return}
print(tag)
if let clickedImageView = CategoryViews(rawValue:tag){
self.delegate?.clicked(view:clickedImageView,forCell:self)
}
}
}
Then in your view controller class you can you have to confirm to the HomeMainCatgCellDelegate protocol and set the cell's delegate to self.
class HomeViewController: DatasourceController, HomeMainCatgCellDelegate{
...
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//Dequeue your cell here
cell.delegate = self
...
return self
}
func clicked(view:CategoryViews,forCell cell:HomeMainCatgCell){
//And you can handle your events accordingly here
if let indexpath = self.collectionView.indexPath(for: cell){
//Here you will get the indexpath of clicked cell and then you can put a switch condition to check which imageview is clicked
switch view{
case .personal:break
case .healthcare:break
case .homecare:break
case .kitchencare:break
}
}
}
}
Hope this helps.
The problem is that your "homeViewcontroller" variable is not set up correctly.
This code:
var homeViewcontroller = HomeViewController()
Creates an instance of a HomeViewController class. But this instance is in no way related to the screen you think it is. The screen that presented your current view is a completely different instance.
To be honest the way you are chaining everything is pretty weird and its not very intuitive. But if you want to fix your problem without altering your code too much you have to pass the REAL HomeViewController downwards.
So on the HomeViewController.swift you have this code:
let homeViewDatasource = HomeViewDatasource()
self.datasource = homeViewDatasource
Pass the "handleCtgClick" as a closure to it.
Something like
homeViewDataSource.presenterClosure = {(ctgname: String ,ctgId: String) in
let dummySettingViewController = UIViewController()
dummySettingViewController.view.backgroundColor = UIColor.white
dummySettingViewController.navigationItem.title = ctgname
navigationController?.navigationBar.tintColor = UIColor.white
navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
navigationController?.pushViewController(dummySettingViewController, animated: true)
}
(Note that you have to create this closure variable declaration within your HomeViewDatasource.swift so that you can assign it from the HomeViewController.swift)
Then repeat this step again to pass it to the HomeMainCatgCell.swift
Then you will be able to call this function referencing the local closure variable you created.
ALTERNATIVELY:
You can just pass the reference to the HomeViewController itself. But if you decide to do this make sure you keep passing it as an "optional" variable with a "weak" property or you will create a retain loop.

Resources