Swift: How to reduce duplicated code when using ViewControllers with identifical buttons - swift

I have the following code on Swift:
class ResenhaMarcasFocinhoController: UIViewController {
var rotateMaisButton:UIButton! = nil
var rotateMenosButton:UIButton! = nil
var lixeiraButton:UIButton! = nil
var confirmarButton:UIButton! = nil
var desfazerVermelhoButton:UIButton! = nil
var desfazerPretoButton:UIButton! = nil
override func viewDidLoad() {
super.viewDidLoad()
inflateView()
}
func inflateView() {
rotateMaisButton = SubViewHelper.getButtonByText(buttonText: "rotate_mais", currentView: view)
rotateMaisButton.addTarget(self, action: #selector(rotateMais_Clicked), for: .touchUpInside)
rotateMenosButton = SubViewHelper.getButtonByText(buttonText: "rotate_menos", currentView: view)
rotateMenosButton.addTarget(self, action: #selector(rotateMenos_Clicked), for: .touchUpInside)
lixeiraButton = SubViewHelper.getButtonByText(buttonText: "lixeira", currentView: view)
lixeiraButton.addTarget(self, action: #selector(lixeira_Clicked), for: .touchUpInside)
confirmarButton = SubViewHelper.getButtonByText(buttonText: "confirmar", currentView: view)
confirmarButton.addTarget(self, action: #selector(confirmar_Clicked), for: .touchUpInside)
desfazerVermelhoButton = SubViewHelper.getButtonByText(buttonText: "desfazer_vermelho", currentView: view)
desfazerVermelhoButton.addTarget(self, action: #selector(desfazerVermelho_Clicked), for: .touchUpInside)
desfazerPretoButton = SubViewHelper.getButtonByText(buttonText: "desfazer_preto", currentView: view)
desfazerPretoButton.addTarget(self, action: #selector(desfazerPreto_Clicked), for: .touchUpInside)
}
func rotateMais_Clicked(sender: UIButton) {
// some code
}
func rotateMenos_Clicked(sender: UIButton) {
// some code
}
func lixeira_Clicked(sender: UIButton) {
// some code
}
func confirmar_Clicked(sender: UIButton) {
// some code
}
func desfazerVermelho_Clicked(sender: UIButton) {
// some code
}
func desfazerPreto_Clicked(sender: UIButton) {
// some code
}
}
The function SubViewHelper.getButtonByText is only a way to assign the button based in the text.
But my problem here is that I have 4 UIViewControllers that contains the same code, because each UIView has the same buttons.
Is it possible to reduce the code on each view controller?

ViewControllers are just classes, and they can participate in inheritance just like any class. So define a base ViewController
class BaseViewController: UIViewController
{
// Common implementation
}
and then inherit from it:
class ViewController1: BaseViewController {
...
}
class ViewController2: BaseViewController {
...
}
...
You can call super for the common code.
An alternative approach would be to define a protocol for your view controllers and provide the common implementation in a protocol extension, however since extensions can't store properties, this might not be the best use for protocols.
If you prefer to use composition rather than inheritance, you can put your common code in a separate class that's not even a view controller that you instantiate in your view controllers, and forward whatever you like to it. This is essentially creating your own kind of delegate:
struct MyViewControllerDelegate // Doesn't have to be a class so I made this a struct
{
/* all those common buttons go here */
func viewDidLoad() { /* common code */ }
}
class ViewController: UIViewController
{
var myDelegate = MyViewControllerDelegate()
override func viewDidLoad()
{
super.viewDidLoad()
myDelegate.viewDidLoad()
// whatever special code you need here.
}
...
}
If you find this cropping up for other sets of view controllers, you might be able to define a protocol for your "ViewControllerDelegates" so they provide a common interface. That would make it easier to generalize the solution.

Related

How to navigate from one View Controller to the other?

I want to navigate from one View Controller to another.
let vc = SecondViewController()
I have tried until now :
vc.modalPresentationController = .fullScreen
self.present(vc, animated: true) //self refers to the main view controller
Im trying to open a new ViewController when the users manages to register or to log in.I am new to software developing, and I want to ask, is this the best method to navigate from one ViewController to another, im asking because as I can see the mainViewController is not deinit(). I have found other similar questions and tried the answers, the problem is with the:
self.navigationController?.pushViewController
it doesn't work because I don't have any storyboard.
The question is it is right to navigate as explained above?
Thanks,
Typically when you are doing login you would use neither push or present. There are multiple ways of handling this, but the easiest is to embed in some parent (root) VC. Here is an example:
class ViewController: UIViewController {
private var embeddedViewController: UIViewController! {
didSet {
// https://developer.apple.com/documentation/uikit/view_controllers/creating_a_custom_container_view_controller
// Add the view controller to the container.
addChild(embeddedViewController)
view.addSubview(embeddedViewController.view)
// Create and activate the constraints for the child’s view.
embeddedViewController.view.translatesAutoresizingMaskIntoConstraints = false
embeddedViewController.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
embeddedViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
embeddedViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
embeddedViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// Notify the child view controller that the move is complete.
embeddedViewController.didMove(toParent: self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let loginVC = LoginViewController()
loginVC.delegate = self
embeddedViewController = loginVC
}
}
extension ViewController: LoginDelegate {
func didLogin() {
embeddedViewController = MainViewController()
}
}
protocol LoginDelegate: AnyObject {
func didLogin()
}
class LoginViewController: UIViewController {
private lazy var loginButton: UIButton = {
let button = UIButton()
button.setTitle("Login", for: .normal)
button.addTarget(self, action: #selector(didTapLoginButton), for: .touchUpInside)
return button
}()
weak var delegate: LoginDelegate?
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(loginButton)
view.backgroundColor = .red
loginButton.translatesAutoresizingMaskIntoConstraints = false
loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loginButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
#objc private func didTapLoginButton() {
delegate?.didLogin()
}
}
class MainViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
}
}

Views not performing segue

I have a collection of views and I want to make that when they are tapped, it will perform the same segue. and no view performs any segue.
class ViewController: UIViewController {
#IBOutlet var categoryViews: [UIView]!
let tapGesture = UIGestureRecognizer(target: self, action: #selector(ViewController.move(tap:)))
override func viewDidLoad() {
super.viewDidLoad()
for category in (0..<categoryViews.count) {
categoryViews[category].addGestureRecognizer(tapGesture)
categoryViews[category].isUserInteractionEnabled = true
}
// Do any additional setup after loading the view.
}
#objc func move(tap: UIGestureRecognizer) {
performSegue(withIdentifier: "Animals", sender: nil)
}
}
A single instance of UITapGestureRecognizer can be added to a single view.
In your code, since you're using a single instance of UITapGestureRecognizer for each view, the tapGesture will be added only to the last view in categoryViews array.
You need to create different UITapGestureRecognizer instance for each view in categoryViews, i.e.
class ViewController: UIViewController {
#IBOutlet var categoryViews: [UIView]!
override func viewDidLoad() {
super.viewDidLoad()
categoryViews.forEach {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(move(tap:)))
$0.addGestureRecognizer(tapGesture)
$0.isUserInteractionEnabled = true
}
}
#objc func move(tap: UITapGestureRecognizer) {
performSegue(withIdentifier: "Animals", sender: nil)
}
}
The problem is that this code doesn't do what you think it does:
class ViewController: UIViewController {
let tapGesture = UIGestureRecognizer(target: self, action: #selector(ViewController.move(tap:)))
Your let tapGesture is an instance property declaration, and what follows the equal sign is its initializer. But you can't speak of self in an instance property initializer; there is no instance yet. So self here is taken to be the class. Thus, your tap gesture recognizer "works", but the move message is not sent to your ViewController instance; in effect, it is sent into empty space.
To fix this, you can initialize tapGesture at a time when self does exist. For example:
class ViewController: UIViewController {
let tapGesture : UIGestureRecognizer!
func viewDidLoad() {
self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.move(tap:)))

Protocol doesn't get read in swift xcode project

i have a UITableview that has unique cells,
each cell has it's own class and they have actions that i want to connect to my main UITableviewcontroller
I attach a protocol and open it in the tableviewcontroller
but it doesn't get read
how could I initialise it or what am I doing wrong ?
here is my cell class :
import UIKit
class AddFaxHeadlineTableViewCell: UITableViewCell {
var delegate: AddFaxHeadlineProtocol?
#IBOutlet weak var addButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func onAddFaxNumberPressed(_ sender: Any) {
delegate?.faxButtonPressed()
}
}
protocol AddFaxHeadlineProtocol{
func faxButtonPressed()
}
and in my tableviewcontroller I extend the protocol:
class SummaryMainTableViewController: UITableViewController, AddFaxHeadlineProtocol, AddEmailHeadlineProtocol {
but the function itself never gets read:
func faxButtonPressed() {
var indexToInsert = 0
for forIndex in 0..<sectionsData.count {
// render the tick mark each minute (60 times)
if (sectionsData[forIndex] == "addFaxHeadline") {
indexToInsert = forIndex + 1
}
}
sectionsData.insert("addNewFax", at: indexToInsert)
mainTableView.reloadData()
}
You need to call:
cell.delegate = self
In your cellForRowAtIndex method
This is the common mistake done in protocols and delegates to forget to call delegate.
Here are few examples you can check all have missing is calling delegate:-
Swift delegate beetween two VC without segue
Delegate seems to not be working, according to the console
How to present another view controller after dismiss from navigation controller in swift?
Another way to go inside the vc without protocols
let cell = ///
cell.addButton.tag = indexPath.row
cell.addButton.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
}
#objc func buttonTapped(_ sender:UIButton) {
print(sender.tag)
}
Check that you are doing this:
cell.delegate = self (It's required)
Then improve your line of code like below. Because you will not set delegate then by calling this delegate method directly will get crashed.
#IBAction func onAddFaxNumberPressed(_ sender: Any) {
if let delegateObject = delegate {
delegateObject.faxButtonPressed()
}
}
Second,
In this line, delegateObject.faxButtonPressed(), you will need to send some parameter to identify that will cell is clicked. So you can pass here button tag or you can pass cell also.

Swift 3 Method from Class not working with #selector syntax

I feel as though I'm not understanding the Swift #selectors properly. I'm trying to connect a button to a method from another class.
I have a class to print the button when pushed:
class printThings {
#IBAction func printMe(_ sender: UIButton){
print("Button Pushed.")
}
}
And then the ViewController:
class ViewController : UIViewController {
override func ViewDidLoad(){
super.viewDidLoad()
//button setup here
let printMe = printThings()
button.addTarget(printMe, action: #selector(printMe.printMe(_:)), for: .touchUpInside)
//add button to subview
}
}
This never triggers the print statement in the class. I'm sure I'm missing something simple.
Thanks.
The problem is that printMe is a temporary, local variable:
let printMe = printThings() // local variable
button.addTarget(printMe, action: #selector(printMe.printMe(_:)), for: .touchUpInside)
// ... and then viewDidLoad ends, and `printMe` vanishes
So by the time you push the button, printMe has vanished; there is no one to send the button message to.
If you want this to work, you need to make printMe persist:
class ViewController : UIViewController {
let printMe = printThings() // now it's a _property_ and will persist
override func viewDidLoad(){
super.viewDidLoad()
//button setup here
button.addTarget(self.printMe, action: #selector(self.printMe.printMe(_:)), for: .touchUpInside)
//add button to subview
}
}

I have custom uinavigation class. In that custom delegate method declared. How to access that method to view controller

#objc protocol MyDelegate {
func buttonAction()
}
class CustomNavigationBar: UINavigationController {
var delegte : MyDelegate?
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton.init(frame: CGRectMake(200, 10, 50, 30))
button.setBackgroundImage(UIImage(named: "a.png"), forState: .Normal)
button.addTarget(self, action: "testing", forControlEvents: .TouchUpInside)
self.navigationBar.addSubview(button)
}
func testing(){
self.delegte?.buttonAction()
print("Pavan")
}
If i press this button, testing is calling.
But in viewcontroller calling delegate method but giving error
class ViewController: UIViewController,MyDelegate{
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "hi"
let vc = CustomNavigationBar()
vc.delegte = self
}
func buttonAction() {
print("Tupale")
}
would u mind to hint the error message?
updated:
In CustomNavigationBar class, you have to change var delegte : MyDelegate? to var delegte : UIViewController?.
then in ViewController class, you could set self which is an instance of UIViewController to the delegate of vc.
Have a try.