Getting properties of a UIButton in a UITableViewCell - ios

I am entirely new at Swift, and am building a referral form. Some pages of the form have tickboxes where the user can select what type of support they need. I've created these tickboxes as cells for a UITableView. They have their own view and nib, and are made up of a button and an image for feedback. Here's the code:
class TickboxCell: UITableViewCell {
#IBOutlet weak var supportBox: UIView!
#IBOutlet weak var supportBtn: UIButton!
#IBOutlet weak var checkmark: UIImageView!
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 buttonPressed(_ sender: UIButton) {
if supportBtn.isSelected == false {
supportBtn.isSelected = true
checkmark.image = UIImage(named: K.Images.checked)
} else {
supportBtn.isSelected = false
checkmark.image = UIImage(named: K.Images.notChecked)
}
}
}
In the ViewController for the page, I'm trying to create a function whereby, when the user presses the Next button to go to the next page of the form, all the labels of the buttons that have been selected are stored and then passed onto the final page of the referral form, ready to be emailed.
This is where I'm at with the function:
func getTicks() {
var ticks: [String:String?] = [:]
for cell in self.tableView.visibleCells {
if cell.supportBtn.isSelected == true {
if let title = cell.supportBtn.titleLabel?.text {
ticks.updateValue("Yes", forKey: title)
}
}
}
}
Of course, this doesn't work because the visible cells don't have what I'm looking for. They only have properties like baseClass and frame.
In my function for the tableView, there's the line
let cell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! TickboxCell
I know that's where it makes the tableView cells into the Tickbox cells - I set the titleLabels for the cell buttons after that. So can I work this into the above function somehow? Or is there another way to tap into the selected status and titles of my buttons?
This is my first ever question on here, apologies if there's anything wrong with it!

Related

How to select the current item to delete it from an array in Swift using Realm

So, I have tried looking ALL over the internet and StackOverflow to find an answer, but I'm not sure what to even look for, so the answer may already have been shared. So I'm sorry in advance, as I am a noob. However, I still need help. (please!) I've got an app I'm working on with a tableview full of parts, with a details part page that gives details of the part (Part name, part number, description, etc.)
I have a delete button at the end of the page, and when you click it, it asks you if you want to delete, are you sure? If the user says yes, then the part deletes, but the delete
only deleted the LAST item from the tableview, the most recently added. Which I know, is because I've called the following function:
func deletePart() {
if let partToDelete = getPartsArray().last {
try! realm.write {
realm.delete(partToDelete)
}
}
with 'getPartsArray().last'
I'm trying to see how I can get the CURRENT selected part in the tableview to be deleted. Right now, I could have the second part from the top selected, and if I click THAT part's delete button, it will always delete the last part from the tableview.
Here's the code for the getPartsArray function:
func getPartsArray() -> [PartInfo] {
return getAllParts().map { $0 }
}
I (noobishly) have already tried: with 'getPartsArray().current' and apparently that's not a thing lol.
I was also thinking, since I'm using REALM / Mongo DB, I could find the part by it's ID? and then delete it? but I'm not sure how to find the current select part's id either.
Any help would be much appreciated. Thank you!
EDIT: here is my TableView Code:
//
// ViewAllPartsViewController.swift
// PartKart
//
// Created by Kiarra Julien on 10/20/21.
//
import Foundation
import UIKit
class ViewAllPartsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CurrencyFormatter {
private var brain = PartKartBrain()
private var parts = [PartInfo]()
#IBOutlet var tableView: UITableView!
#IBAction func returnHome() {
dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "DemoTableViewCell", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "DemoTableViewCell")
tableView.delegate = self
tableView.dataSource = self
parts = brain.getPartsArray()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
parts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DemoTableViewCell", for: indexPath) as! DemoTableViewCell
cell.partNameLabel.text = parts[indexPath.row].partName
// Convert string value to double
cell.partCostLabel.text = formatCurrency(value: parts[indexPath.row].partCost)
// String(format: "$%.2f", parts[indexPath.row].partCost)
cell.purchaseDateLabel.text = parts[indexPath.row].purchaseDate
// cell.textLabel?.text = parts[indexPath.row].partName
// cell.textLabel?.numberOfLines = 0countTotalParts()
// cell.textLabel?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "showPartDetails", sender: parts[indexPath.row])
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
if let viewcontroller = segue.destination as? PartDetailsViewController {
viewcontroller.part = sender as? PartInfo
}
}
}
and here's where I call delete part:
class PartDetailsViewController: UIViewController, CurrencyFormatter {
//Store Information Labels
#IBOutlet weak var storeNameLabel: UILabel!
#IBOutlet weak var storeNumLabel: UILabel!
#IBOutlet weak var storeAddrLabel: UILabel!
//Part Information Labels
#IBOutlet weak var partNameLabel: UILabel!
#IBOutlet weak var partNumLabel: UILabel!
#IBOutlet weak var partDescLabel: UILabel!
#IBOutlet weak var partCostLabel: UILabel!
#IBOutlet weak var partQtyLabel: UILabel!
#IBOutlet weak var purchaseDateLabel: UILabel!
#IBOutlet weak var hasWarrantySwitch: UISwitch!
#IBOutlet weak var warrantyLengthLabel: UILabel!
//Mechanic Information Labels
#IBOutlet weak var mechanicNameLabel: UILabel!
#IBOutlet weak var mechanicNumLabel: UILabel!
#IBOutlet weak var mechanicAddrLabel: UILabel!
#IBOutlet weak var laborCostLabel: UILabel!
#IBOutlet weak var serviceDateLabel: UILabel!
var part: PartInfo?
let brain = PartKartBrain()
#IBAction func deletePartBtn(_ sender: UIButton) {
// Declare Alert message
let dialogMessage = UIAlertController(title: "Confirm", message: "Are you sure you want to delete this part?", preferredStyle: .alert)
// Create OK button with action handler
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
// I CALL DELETE PART RIGHT HEREEE!
self.brain.deletePart()
// delay and then dismiss the page
let delayInSeconds = 0.5
DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { [unowned self] in
dismiss(animated: true, completion: nil)
}
})
// Create Cancel button with action handlder
let cancel = UIAlertAction(title: "Cancel", style: .cancel) { (action) -> Void in
print("Cancel button tapped")
}
//Add OK and Cancel button to dialog message
dialogMessage.addAction(ok)
dialogMessage.addAction(cancel)
// Present dialog message to user
self.present(dialogMessage, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
title = part?.partName
//Set the Store Info Labels Equal to actual data
storeNameLabel.text = part?.partName
storeNumLabel.text = part?.storeNumber
storeAddrLabel.text = part?.storeAddress // < ---- The address is cut off the screen!
//Set the Part Info Labels Equal to actual data
partNameLabel.text = part?.partName
partNumLabel.text = part?.partNumber
partDescLabel.text = part?.partDescription
if let partCost = part?.partCost {
partCostLabel.text = formatCurrency(value: partCost)
}
if let partQty = part?.partQuantity {
partQtyLabel.text = String(partQty)
}
purchaseDateLabel.text = part?.purchaseDate
//If there's no warranty, display 'N/A' instead
if part?.hasWarranty == true {
hasWarrantySwitch.isOn = true
warrantyLengthLabel.text = part?.warrantyLength
} else {
hasWarrantySwitch.isOn = false
warrantyLengthLabel.text = "N/A"
}
//Set the Mechanic Info Labels Equal to actual data
mechanicNameLabel.text = part?.mechanicName
mechanicNumLabel.text = part?.mechanicNumber
mechanicAddrLabel.text = part?.mechanicAddress
//laborCostLabel.text = part?.laborCost
if let laborCost = part?.laborCost {
laborCostLabel.text = formatCurrency(value: laborCost)
}
serviceDateLabel.text = part?.serviceDate
}
}
Let me state the question back to you
"How to I delete a selected row from my tableview"?
If that's the question let be provide a boiled down answer
First, don't do this when working with Realm
return getAllParts().map { $0 }
Realm objects are lazily loaded into a Results object. As soon as you run them against a high level function like map, reduce etc (storing them in an Array), they ALL get loaded into memory and if you have a large dataset, that will overwhelm the device. Additionally if you have sorting etc, your objects will get out of sync with the underlying Realm data.
Your best bet is to leverage Realm Results as your tableView dataSource - it behaves much like an array.
So here's a viewController class that has a parts Results object as a tableView datasource
class InStockVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
var partsNotificationToken: NotificationToken!
var partsResults: Results<PartsClass>! //the tableView dataSource
Assume the user taps or selects row #2 and then taps or clicks 'Delete'
//pseudocode
let selectedRow = tableView.selectedRow
let part = partResults[selectedRow]
try! realm.write {
realm.delete(part)
}
The above code determines which row in the table was selected, gets the part object from Results and then tells Realm to delete it.
As soon as that object is deleted from Realm, the partsResults object reflects that change and the object is automagically removed! The only thing you need to do is to reflect that change in the UI.
There are many ways of handling that with animations and so forth but to keep it simple, lets just reload the tableView so the deleted row is no longer displayed.
Note in the above code, there's also a var partsNotificationToken: NotificationToken!, that token is an observer of the results object - when something in the underlying data changes, the results object changes as well and the notification token fires an observer action to handle that change. Here's an example observer
self.partsNotificationToken = self.partsResults.observe { changes in
switch changes {
case .initial:
self.tableView.reloadData() //update the tableview after data is initially loaded
case .update(_, _, _, _):
self.tableView.reloadData() //update the tableView after changes

when UITableViewCell layout and constraints get final?

I have a label in my TableViewCell and I will set it's text throw a variable in cell (for example myText), like this:
class ProjectManagementTableViewCell: UITableViewCell {
#IBOutlet weak var myLabel: UILabel!
var myText: String? {
didSet {
if let _myText = self.myText {
self.myLabel.text = _myText
} else {
self.myLabel.text = ""
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I didn't fixed myLabel width size in InterfaceBuilder, so I expect it get resize according to text it gets from cellForRowAt function. everything good till now. but I want label width to be less than some value, and beside this, I have an image that I want to be 10 pt distance from end of text of label. so I need to have access to label width or position of UIElements after they get repositioned in response to assigning text to label. I tried several locations to find when I can access new values for frame positions, but I failed, like awakeFromNib and didSet of myText. so when I can access to final values of frames and constraints in UITableViewCell?
Actually you don't need
var myText: String? {
didSet {
if let _myText = self.myText {
self.myLabel.text = _myText
} else {
self.myLabel.text = ""
}
}
}
just assign the text inside
let cell = //
cell.lbl.text = "text_here"
or create a configure function inside the custom cell class and call it
the label will resize according to the the current constraints
also final frames inside
override func layoutSubviews()
super.layoutSubviews()
}

Disable all UIButtons in UITableViewCell once a separate UIButton is pressed

Problem I've found some questions asking how to disable a particular cell button in a table view, but what I want to do is to disable all instances of a button within a table view cell when another button is pressed.
Details I have a table view which is displaying a list of exercises and number of reps in a custom cell. Within the custom cell is also a button "swap" which allows a user to swap an exercise for another one before the workout starts.
When the user hits "start workout" (which triggers a timer) I want to disable all instances of the swap button (grey them all out and make non clickable).
Code
My workout cell class is here :
class WorkoutCell : UITableViewCell {
var delegate: WorkoutCellDelegate?
#IBAction func swapButtonPressed(_ sender: Any) {
delegate?.swapButtonTapped(cell: self)
}
#IBOutlet weak var exerciseName: UILabel!
#IBOutlet weak var repsNumber: UILabel!
}
protocol WorkoutCellDelegate {
func swapButtonTapped(cell: WorkoutCell)
}
What have I tried
The way I thought to do this was to add an IBOutlet (e.g. 'swapButton') for the button and then simply do something like :
#IBAction func startButtonPressed(_ sender: UIButton) {
WorkoutCell.swapButton.isenabled = false
}
But Xcode doesn't allow you to add IBOutlets to repeating cells so I'm a bit stuck.
I'm fairly new to delegates (managed to get it working for displaying the table view) so if it has something simple to do with that sorry!
Add a property to your viewcontroller:
var swapButtonsDisabled : Bool = false
In your cellForRow do something like this:
cell.swapButton.isEnabled = !self.swapButtonsDisabled
When the start button is pressed, set swapButtonDisabled to true and reload the tableView.
1- As you connect
#IBOutlet weak var exerciseName: UILabel!
create outlet for every btn
#IBOutlet weak var btn1: UIButton!
2- Add a property to the model array in the VC to hold the state of every cell
3- When you click the main btn fire the delegate method with the btn's cell
4- In VC delegate handle method disable the btns and change the state of the array index path
5- Don't forget to check state in cellForRow
You are pretty close. First I suggest you to be more specific and have the data you need in cell and use access control:
class WorkoutCell : UITableViewCell {
var workoutSwappable: (workout: Workout, canBeSwapped: Bool)? {
didSet {
swapButton.isEnabled = workoutSwappable?.canBeSwapped == true
// TODO: do additional setup here
}
}
weak var delegate: WorkoutCellDelegate? // Needs to be weak or you will have memory leaks
#IBAction private func swapButtonPressed(_ sender: Any) {
if let workoutSwappable = workoutSwappable, workoutSwappable.canBeSwapped == true {
delegate?.workoutCell(self, didTapWorkoutSwap: workoutSwappable.workout)
}
}
#IBOutlet private var exerciseName: UILabel!
#IBOutlet private var repsNumber: UILabel!
#IBOutlet private var swapButton: UIButton!
}
Ok so now in cell for row at index path all you need is something like:
cell.workoutSwappable = (self.items[0], self.canSwap)
On delegate you now have:
func workoutCell(_ sender: WorkoutCell, didTapWorkoutSwap workout: workout) {
self.currentWorkout = workout
self.canSwap = false
self.initializeTimer()
tableView.reloadData() // This will now flush all the buttons
}

setTitle for UIButton results in it disappearing

I have changed the alpha of the button successfully so I know I can modify it. But when I use setTitle, the button just disappears.
I have a tableview with collection views in each cell. I have a custom header cell that when tapped, presents options including the button that disappears when tapping on it.
Here is the function in the main view controller that is called properly and works (I know because I can change the alpha of the button and even make it hidden).
func deleteScenes(){
if let header = whichHeaderTapped {
header.deleteScenesOutlet.setTitle("fuckssake", for: .normal)
header.deleteActOutlet.alpha = 0.0
header.editButtonOutlet.alpha = 0.0
}
}
Here is the class for the header cell.
class HeaderCell: UITableViewCell {
static var shared = HeaderCell()
#IBOutlet var headerName: UILabel!
#IBOutlet var newOptions: UIStackView!
#IBOutlet var deleteScenesOutlet: UIButton!
#IBOutlet var deleteActOutlet: UIButton!
#IBOutlet var editButtonOutlet: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
self.newOptions.frame.origin = CGPoint(x: 285, y: 0)
}
override func layoutSubviews() {
super.layoutSubviews()
}
#IBAction func deleteScenesButton(_ sender: UIButton) {
if deleteScenesOutlet.titleLabel?.text == "Delete Scenes" {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "deleteScenesButtonEnabled"), object: nil)
}
else {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "deleteScenesButtonDisabled"), object: nil)
}
}
}
Your code seems to be fine. Can you check what is the color of your button title. If it is white and your background view color is also white then there is a chance you were not able to see the button.

UIButton (checkbox) in custom UITableView subclass - Swift

I asked a version of this question before but I failed to mention my UIButton checkboxes were in a UITableVlewCell. I was told to subclass this from a UITableViewCell instead of a UIButton. It's a to-do list app so I'm making this in a prototype cell. Currently, I'm able to run the app but not save the state of the checkbox using NSUserDefaults. I've followed a tutorial from YouTube and even downloaded the files along with it and converted them from Objective-C to Swift. When I tried to put all the code in the View Controller without a subclass, I got the error message "..Outlets cannot be connected to repeating content." In the subclass, I'm able to run the app but nothing saves. I'm also unsure of what else to call in my viewDidLoad() method.
My code is as follows:
override func viewDidLoad() {
super.viewDidLoad()
// NSUserDefaults
checked = defaults.boolForKey("boxIsChecked")
}
My subclass:
class TableViewCell: UITableViewCell {
var defaults = NSUserDefaults.standardUserDefaults()
let isSelected = UIImage(named: "Selected")
let unselected = UIImage(named: "Unselected")
#IBOutlet weak var checkBoxButton: UIButton!
#IBAction func checkBox(sender: UIButton) {
if (!checked) {
checked = true
checkBoxButton.setImage(isSelected, forState: UIControlState.Normal)
defaults.setBool(checked, forKey: "boxIsChecked")
}
else if (checked) {
checked = false
checkBoxButton.setImage(unselected, forState: UIControlState.Normal)
defaults.setBool(checked, forKey: "boxIsChecked")
}
}
func checkTheBox() {
if (!checked) {
checkBoxButton.setImage(unselected, forState: UIControlState.Normal)
}
else if (checked) {
checkBoxButton.setImage(isSelected, forState: UIControlState.Normal)
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
NOTE: var checked: Bool = true is a global variable in my first View Controller. I'm unsure if I need to initialize this as var checked: Bool. I have tried and got many error messages. Thank you for your help.
ALSO:: When I add TableViewCell().checkTheBox() to the viewDidLoad() method, I get a EXC_BAD_INSTRUCTION on the statement under else if in the checkTheBox method.

Resources