Better way of handling xib button press than chained delegates? - ios

I have implemented some code including protocols to chain the active instance up to the VC to allow for Segues and am wondering if there's a better way to solve this problem or if I'm handling the Segue the wrong way.
Segues within child classes do not work because they are not in the primary VC
Excerpt from VC file
class ViewController: UIViewController, UITableViewDelegate, TableViewControllerDelegate {
func didButtonPressed() {
performSegue(withIdentifier: "destStory", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
tableViewController.delegate = self
}
}
protocol TableViewControllerDelegate {
func didButtonPressed()
}
class TableViewController: UITableViewController, TableViewCellDelegate {
func didButtonPressed() {
delegate?.didButtonPressed()
}
let test = ["test1", "test2", "test3"]
var instanceOf = self
var delegate: TableViewControllerDelegate?
Excerpt from cell file
protocol TableViewCellDelegate {
func didButtonPressed()
}
class TableViewCell: UITableViewCell {
var delegate: TableViewCellDelegate?
#IBOutlet weak var labelOne: UILabel!
#IBOutlet weak var labelTwo: UILabel!
#IBOutlet weak var buttonOne: UIButton!
#IBAction func buttonOnePressed(_ sender: Any) {
delegate?.didButtonPressed()
}
This functions the way I'd expect but seems potentially confusing to work with.

Another option is using closures instead of delegate.
class TableViewCell: UITableViewCell {
var buttonPressedClosure: (() -> Void))?
#IBOutlet weak var labelOne: UILabel!
#IBOutlet weak var labelTwo: UILabel!
#IBOutlet weak var buttonOne: UIButton!
#IBAction func buttonOnePressed(_ sender: Any) {
buttonPressedClosure?()
}
}
Also, you can add target action for cell's button inside cellForRow UITableViewDelegate function directly, but it is a dirty way in my opinion.
let cell = ...
cell.button.addTarget(self, action: #selector(didButtonPressed), for: .touchUpInside)

Related

How to implement event on click in button inside in a tableView to show other element?

I have a tableView with action buttons, one of them are hide until the user click the other button, I was looking how to do that and I found that I have to implement a delegate like the code below:
Class TableViewCell:
import UIKit
import FLAnimatedImage
protocol OnButtonsClickDelegate:AnyObject{
func onBtnDownloadClick(cell: ListadoTableViewCell)
}
class ListadoTableViewCell: UITableViewCell {
#IBOutlet weak var lblAnterior: UILabel!
#IBOutlet weak var lblCompras: UILabel!
#IBOutlet weak var lblDevolucion: UILabel!
#IBOutlet weak var lblSaldo: UILabel!
#IBOutlet weak var lblAbonos: UILabel!
#IBOutlet weak var lblNuevo: UILabel!
#IBOutlet weak var lblDiferido: UILabel!
#IBOutlet weak var lblCliente: UILabel!
#IBOutlet weak var lblNombreCliente: UILabel!
#IBOutlet weak var spinner: FLAnimatedImageView!
#IBOutlet weak var btnDowload: UIButton!
#IBOutlet weak var btnShare: UIButton!
var onButtonsClickDelegate : OnButtonsClickDelegate!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func onBtnDownloadClick(_ sender: AnyObject){
onButtonsClickDelegate.onBtnDownloadClick(cell: self)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Class Controller:
class ListadoController: NavigationViewController, UITableViewDelegate, UITableViewDataSource, RefreshScrollViewDelegate,OnButtonsClickDelegate {
#IBOutlet weak var tableView: RefreshTableView!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellListado") as! ListadoTableViewCell
let r = data[indexPath.row]
let compras = Functions.stringToFloat(str: r.compras)
let comprasn = Functions.stringToFloat(str: r.comprasn)
let abonos = Functions.stringToFloat(str: r.abonos)
let diferido = Functions.stringToFloat(str: r.diferido)
let saldomov = Functions.stringToFloat(str: r.saldomov)
cell.lblAnterior.text = Functions.moneyFormat(n: saldomov - compras - comprasn)
cell.lblCompras.text = Functions.moneyFormat(n: compras)
cell.lblDevolucion.text = Functions.moneyFormat(n: 0.0)
cell.lblSaldo.text = Functions.moneyFormat(n: saldomov - comprasn)
cell.lblAbonos.text = Functions.moneyFormat(n: abonos) + ""
cell.lblNuevo.text = Functions.moneyFormat(n: saldomov - comprasn - abonos) + ""
cell.lblDiferido.text = Functions.moneyFormat(n: diferido) + ""
cell.lblCliente.text = r.nombre.capitalized
cell.lblNombreCliente.text = r.cvecte
cell.onButtonsClickDelegate = self
if indexPath.row == data.count - 1 {
if (!last && !loading) {
loadData(page: currentPage)
}
}
return cell
}
func onBtnDownloadClick(cell: ListadoTableViewCell) {
cell.btnShare.isHidden = false
}
}
The problem is that it does not work correctly. When the user clicks the button, the other element is displayed but not only in the selected row, but also in other rows as well, how can I solve this problem?
The cell is being re-used and whatever is the state of that cell will still be there, in which case, try adding this to ListadoTableViewCell:
override func prepareForReuse() {
super.prepareForReuse()
btnShare.isHidden = true
}
You should try to save the state of the cell when you press the download button so that when the reloadData of the TableView is performed and the cellForRowAt function is called, the state of the changes in the cells is preserved, in your case the share button is shown if the download button has previously been pressed.
Here there is a project from my Github that make the functionality you need by applying MVVM Pattern https://github.com/JLPenaLopez/MyFiles I hope this helps you
This is a demostration: https://github.com/JLPenaLopez/MyFiles/blob/master/MyFilesGif.gif
I solved using an answer from another post:
Button action in custom UITableViewCell affects other cells
Thank you for your help.

Swift Delegate returns unexpectedly found nil while unwrapping an Optional value

Setting up my delegate within the protocol continues to throw nil. I have attempted numerous approaches suggested in other posts. I am attempting to get my protocol + delegate up and running, however, unable to solve why it continues to throw nil.
Force unwrapping, calling the delegate from various locations in the file, removing & adding Weak Var.
List View:
protocol MixPlayer : class {
func playMix(message: String)
}
class IssueViewController: UIViewController {
#IBOutlet weak var issueCollection: UICollectionView!
#IBOutlet weak var issueImage: UIImageView!
var viewController: ViewController?
var collectionViewtitle: String?
var mixImageName: String?
var mixList: [[String: String]]!
weak var mixDelegate: MixPlayer?
override func viewDidLoad() {
super.viewDidLoad()
issueCollection.dataSource = self
issueCollection.delegate = self
}
}
....
extension IssueViewController: UICollectionViewDelegate, UICollectionViewDataSource {
....
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let url = mixList[indexPath.row]["url"] {
mixDelegate?.playMix(message: url)
}
}
}
View Controller:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var mainContainer: UIView!
#IBOutlet weak var playerEmbedView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let secondVC = IssueViewController()
secondVC.mixDelegate = self
}
}
extension ViewController: MixPlayer {
func playMix(message: String) {
print(message)
}
}
Any attempt at calling
mixDelegate?.playMix(message: url)
Is unsuccessful. Currently I'm just trying to log basic print statements.
Probably your secondVC inside the ViewController gets deallocated. Make that a property in your ViewController.
class ViewController: UIViewController {
#IBOutlet weak var mainContainer: UIView!
#IBOutlet weak var playerEmbedView: UIView!
lazy var secondVC: IssueViewController = {
let secondVC = IssueViewController()
secondVC.mixDelegate = self
return secondVC
}()
override func viewDidLoad() {
super.viewDidLoad()
}
...
}
To know if your class gets deallocated, add a deinit with a print statement on it.
deinit {
print("Deallocated")
}

Calling a function that will hide a button from another class - swift

I'm trying to create an IMDB-ish movie application where when the user doesn't login and presses a Skip button, a Favorite (add a movie to favourites) button would dissappear.
The LandingViewController is where the Skip button is and the MovieTableViewCell is where all the data is presented.
What must I do so that the hide action will work inside the function? what's the logic behind this? what am I missing?
Kindly check the below comments in the code. thanks !
import UIKit
class LandingViewController: UIViewController {
#IBOutlet weak var skipButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func didTapSkip(_ sender: Any) {
MovieTableViewCell().hideButton()
}
import UIKit
class MovieTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var ratingLabel: UILabel!
#IBOutlet weak var languageLabel: UILabel!
#IBOutlet weak var releaseYearLabel: UILabel!
#IBOutlet weak var posterImage: UIImageView!
#IBOutlet weak var favoriteButton: UIButton?
override func awakeFromNib() {
super.awakeFromNib()
favoriteButton?.isHidden = true // works here for some reason
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func hideButton() {
favoriteButton?.isHidden = true // doesn't work and If I unwrap it, the app will crash, because it will find the buttton as NIL for some reason
print("hidebutton") // the print works so the function works when the skip is pressed
}
Try using NSUserDefaults
In LandingViewController
#IBAction func didTapSkip(_ sender: Any) {
UserDefaults.standard.set(true, forKey: "didSkip")
}
In MovieTableViewCell
override func awakeFromNib() {
super.awakeFromNib()
if UserDefaults.standard.bool(forKey: "didSkip") {
favoriteButton?.isHidden = true
} else {
favoriteButton?.isHidden = false
}
}

"fatal error" when calling tableView.reloadData(), the tableView is properly connected

I am trying to dynamically update a tableView while the program is running. I believe I have updated the array that the data loads from correctly, but when I press the button that calls self.eventTable.reloadData() I receive the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Here is the relevant code:
View Controller:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
//Timer view
#IBOutlet weak var playButton: UIBarButtonItem!;
#IBOutlet weak var pauseButton: UIBarButtonItem!;
#IBOutlet weak var refreshButton: UIBarButtonItem!;
#IBOutlet weak var timerLabel: UILabel!
var counter = 0
var timer = Timer()
var isTimerRunning = false
//testing view container
var viewShowing = 1;
override func viewDidLoad() {
// Do any additional setup after loading the view, typically from a nib.
pauseButton.isEnabled = false
hideAll();
self.basicContainer.isUserInteractionEnabled = true;
self.basicContainer.isHidden = false;
self.timerLabel.text = String("00:00:00");
eventTable.dataSource = self
eventTable.delegate = self
super.viewDidLoad()
loadEvents(event: "timer start")
}
...
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Add table to keep track of events
#IBOutlet weak var eventTable: UITableView!
var eventData = [Session]()
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventData.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellReuseIdentifier") as! eventTableViewCell
let event = eventData[indexPath.row]
cell.eventLabel.text = event.session
return cell
}
private func loadEvents(event: String) {
guard let event1 = Session(session: event) else {
fatalError("Unable to instantiate event")
}
eventData += [event1]
DispatchQueue.main.async() {
self.eventTable.reloadData()
}
}
func testPrint() {
loadEvents(event: "testing cell adding")
//self.eventTable.reloadData()
viewWillAppear(false)
print("This is a test print");
}
}
The function works fine when it is called in ViewDidLoad(), but not when it is called by the button in another class ("This is a test print" prints to console so I know the button call is going through).
Expected behavior is the tableView (eventTable) reloading showing two cells, "timer start" and "testing cell adding" (ideally with "testing cell adding" being at the top).
Also want to emphasize that eventTable is connected to the storyboard, which seems to be a common problem on here.
Here is the Session.swift file and the eventTableViewCell.swift file if those are helpful:
Session.swift
import Foundation
import UIKit
class Session {
//MARK: Properties
var session: String
//MARK: Initialization
init?(session: String) {
guard !session.isEmpty else {
return nil
}
self.session = session
}
}
eventTableViewCell.swift
import Foundation
import UIKit
class eventTableViewCell: UITableViewCell {
//MARK: Properties
#IBOutlet weak var eventLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Thanks!
Edit: The ViewController from where I call testPrint().
import UIKit
class BasicViewController: UIViewController {
var VC = ViewController();
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//Basic buttons
#IBOutlet weak var warmButton: UIButton!
#IBOutlet weak var dryButton: UIButton!
#IBOutlet weak var stimulateButton: UIButton!
#IBOutlet weak var controlButton: UIButton!
#IBOutlet weak var bedButton: UIButton!
#IBOutlet weak var tempButton: UIButton!
#IBOutlet weak var pulseButton: UIButton!
#IBOutlet weak var ecgButton: UIButton!
#IBOutlet weak var apgarButton: UIButton!
#IBOutlet weak var helpButton: UIButton!
//APGAR options
#IBOutlet weak var skinColor: UIButton!
#IBOutlet weak var pulse: UIButton!
#IBOutlet weak var grimace: UIButton!
#IBOutlet weak var flexion: UIButton!
#IBOutlet weak var respiratoryEffort: UIButton!
#IBAction func warmButton(sender: AnyObject) {
VC.testPrint();
}
}
It would seem that you are all right in stating that I am instantiating a new ViewController which is causing the issue. How should I go about fixing this? Fairly new to Swift
I think, your problem is in this lines of codes:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellReuseIdentifier") as! eventTableViewCell
let event = eventData[indexPath.row]
cell.eventLabel.text = event.session
return cell
}
Can you check the cell identifier is same as your cell identifier
And number of rows in eventData array

Passing a variable's value from a ViewController to another ViewController where Segue is not possible

I have just started to learn Swift and I'm struggling with a few problems, one of them being this:
I have the following app: ViewController1 -> ViewController2 -> ViewController3.
How can I pass a value from ViewController1 to ViewController3?
I was thinking on creating a static variable (see in the code, var CNP) in the class corresponding to ViewController1 and access it by creating an instance of this class in ViewController 3, but unfortunately I can't manage to do it.
Here is my ViewController1:
class PageCNP: UIViewController {
#IBOutlet weak var labelCNP: UITextField!
#IBOutlet weak var labelDoB: UITextField!
internal var CNP: String = labelCNP.txt //why can't I do that?
override func viewDidLoad() {
//some code here
}
override func didReceiveMemoryWarning() {
//some code here
}
func extractYearOfBirth() -> NSString
{
//some code here
}
#IBAction func verifyCNP(sender: AnyObject) {
//some code here
}
In ViewController3 I want to do this (if it's correct):
#IBOutlet weak var cnpClient: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
var x = PageCNP()
cnpClient.text = x.CNP
}

Resources