UITableViewCell with undetermined number of items - swift

I need to make a cell with some buttons. This buttons are answers options to a question. But the question and the number of buttons (answers) are not determined because I get these data from Alamofire. So there may be 2, 3, 6, or any number of buttons.
Is there a way to add these buttons programmatically depending on (for example) the number of items in an array, in a way that they look well constrained? Like a stack view?
Thanks in advance

A stack view is certainly a way of constraining any number of views into a column (or row). So that sounds like the best way to go.

This is a sneaky way to do it, but if you know the maximum number of buttons you're going to have, you can add it directly to your custom prototype cell on your storyboard without adding buttons programatically.
Solution (Written in Swift 4, but a general answer)
Assuming there is a maximum of 8 answers in any question, create 8 buttons in your custom prototype cell. Add constraints as you like to make them look pretty!
Remember to give your cell a unique identifier ("CustomCell" in my case).
Go to File > New File > Cocoa Touch Class > Select "TableViewCell" Subclass
& give it an appropriate name (in my case CustomCellTableViewCell.swift). This will contain all your UI components outlets.
Select the prototype cell in your storyboard. Navigate to the third icon from the left and select "CustomCellTableViewCell" as your class.
Control+Drag outlets of your buttons and fields that you're going to be working with from the storyboard to your newly created CustomCellTableViewCell.swift. The file should now look something like this:
class CustomCellTableViewCell: UITableViewCell {
#IBOutlet weak var question: UILabel!
#IBOutlet weak var answerOne: UIButton!
#IBOutlet weak var answerTwo: UIButton!
...
...
#IBOutlet weak var answerEight: UIButton!
}
The next few steps will be in the TableviewDataSource function func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell. Remember to cast the TableviewCell as a CustomCell:
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell") as! CustomCellTableViewCell
Put all the answer buttons in an array
var answerButtons = [cell.answerButtonOne, cell.answerButtonTwo..., cell.answerButtonEight]
Now you can set the answer button text in a loop as follows:
for i in 0..<answerButtons.count {
if i < data[indexPath.row].answers.count { // if the answer exists, add it to the button
cell.answerButton[i].isHidden = false // show button if answer exists
cell.answerButton[i].setTitle(data[indexPath.row], for: .normal)
} else {
cell.answerButton[i].isHidden = true // hide that button
}
}

Related

UITableViewCell with button inside in Swift [duplicate]

This question already has answers here:
How to add a button with click event on UITableViewCell in Swift?
(4 answers)
Closed 1 year ago.
I have a UITableView with cells, every cell have information of products, the name of the product and the Id of the product. Every cell has a button that drives you to another screen with more details of the product.
My problem is that when I selects a cell, I have obtain the name and id of the product that belongs to the selected cell thanks to didSelectRowAtIndexPath, but when I touch the button inside the cell I don't trigger didSelectRowAtIndexPath and I can't obtain the id of the product, because of this I can't go to the next screen and show the information related to the selected product.
What could I do?
The question I will ask you which will help solve this is:
Are you using IBAction to transition as the action trigger for the button?
If Yes, then the IBAction will sit under the UITableViewCell which should contain IBOutlet for the UILabels holding the value of name, id and product information.
For example:
class SampleCell: UITableViewCell {
#IBOutlet var nameLabel: UILabel!
#IBOutlet var idLabel: UILabel!
#IBOutlet var informationLabel: UILabel!
#IBAction func action(button: UIButton) {
// you can obtain your information once the button is triggered before navigating
let id = self.idLabel.text
let name = self.nameLabel.text
let information = self.informationLabel.text
// navigate to new screen using delegate protocols
}
}
if you are not using IBOutlets and IBActions as follows, then I will suggest you use them, and create a custom TableViewCell for the product cell. It is the easiest way to access your cell information

Enabling access to data and displaying that data on two different types of Views

I am accessing a Realm database to load values in my iOS app. The first one works fine. I have a UIViewController with UITextFields. The big picture is that i:
Define the outlets in the ViewController and connect them to the mainstoryboard UITextFields. There are three fields
I set up an array and load the results of the Realm query from a PersonData.swift data model. Three properties defined in the swift file.
In viewDidLoad I call the function to loadPersonData.
The first thing in the loadPersonData func is to put the realm.objects including three properties into the three fields that are defined by the Outlets at the top of the ViewController.
They load perfectly, are editable, and I can save the values of the data entry from the screen to the Realm database with a Realm modify. I can show you this code if you want to see it.
This is all working fine. Now my problem. My next View is a UITableViewController, with prototype cell, and three UILabels in each single cell row. This code has in the past used an Array for testing and that worked fine to load sample data back to the rows. I want to now read Realm data and scroll up and down with the three fields in each row. Later, when I select one,I will expose in another View all of the properties of that object called Year which is approximately 10 properties or UITextFields that can be edited.
I was told to build a subclass for the protype cell that will be a sub to the UITableViewCell.
class BYMyCell: UITableViewCell {
I put the Outlets for the three UILabels from the prototype cell into the BYMyCell.swift subclass. These outlets are connected to the UILabels defined in the prototype cell.
I saw one suggestion about putting the same outlets in the first businessYearViewController where all the row support functions were located. I was told to add the = nil to these outlets.
#IBOutlet weak var yearLabel: UILabel! = nil
#IBOutlet weak var descriptionLabel: UILabel! = nil
#IBOutlet weak var startingDateLabel: UILabel! = nil
Now when I try to move the Results of the Realm query from the three properties to the three Labels there appears to be nil in all the fields. I can see the data come into my businessYearViewController where I make the query, but I am having trouble putting the data into the Outlets in the BYMyCell.swift data model for exposure on the view/screen.
If I should post my two files, I will try to put them in a response. Thanks for your help. I'm pretty sure this has something to do with my lack of knowledge of processing TableViewController and prototype cell coordination. My rows are simple. One row, three labels. I will also have an add button at the top right corner and the ability to edit the fields in another View for data entry and changes.
Edited one hour later with information. This is my loadBusinessYear func.
func loadBusinessYearData() {
businessYearArray = realm.objects(YrData.self)
print(businessYearArray!.count)
print(businessYearArray?[0])
if businessYearArray!.count > 0 {
yearLabel.text = businessYearArray?[0].year
descriptionLabel.text = businessYearArray?[0].yearType
startingDateLabel.text = businessYearArray?[0].startingDate
} else {
yearLabel.text = "Sample Name"
descriptionLabel.text = "Sample .com"
startingDateLabel.text = "Sample Date"
}
}
The two print statements at the top give you the 1 for the count and the object properties as Optionals.
1
Optional(YrData {
ydID = 0;
year = 2020;
yearType = Business Open;
startingDate = 12/28/2019;
firstWeekEndDate = 01/03/2020;
lastWeekEndDate = 01/03/2021;
firstQtrTotal = 520;
secondQtrTotal = 520;
thirdQtrTotal = 520;
fourthQtrTotal = 520;
leapYearExtraWeek = 40;
yearContains53Weeks = 1;
laborReporting = List<LaborReportData> <0x600000a15290> (
);
expenseAccounting = List<ExpenseAccountData> <0x600000a15440> (
);
})
The data subclass is BYMyCell swift file.
import UIKit
class BYMyCell: UITableViewCell {
#IBOutlet weak var yearLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var startingDateLabel: UILabel!
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
}
}

Setting the text of a UILabel with a constant

I am a total newby to xCode and Swift so please excuse me if this question is silly.
I am trying to update 2 UILabels and a UIImageView by using the sender.currentTitle of the button pressed.
If a user presses the 'Honduras' button then I want to populate the 2 UILabel with it's corresponding information and the UIImageView with its corresponding image file.
I have the first UILabel working and the UIImageView working but cannot find a way to populate the 2nd UILabel with its corresponding text (which is stored in a constant).
UILabel 1 is called #IBOutlet weak var countryLabel: UILabel!
UILabel 2 is called #IBOutlet weak var peopleLabel: UILabel!
UImageView is called #IBOutlet weak var peopleUIImageView: UIImageView!
The information/file name I want to pass into these 3 are called:
"Honduras"
let peopleHondurasText = "The Lenca"
"peopleHonduras.png"
I have the UILabel 1 working like this:
countryLabel.text = sender.currentTitle
I have the image working by calling this func:
func findCountryImages() {
peopleUIImageView.image = UIImage(named: "people\(countryLabel.text! .filter { !" ".contains($0) }).png")
}
and I am trying to call the constant "peopleHondurasText" like I did the UIImage but it isn't working.
Here is my very amateur code:
peopleLabel.text = "people\(countryLabel.text! .filter { !" ".contains($0) })Text"
Where this:
"people\(countryLabel.text! .filter { !" ".contains($0) })Text"
Should be equal to the constant named:
peopleHondurasText
I want to be able to do this so if a user clicks on any country button it will populate those 2 UILabels and UIImageViews with that countries text and images.
I know I must be doing something fundamentally incorrect or maybe going about this complete in the wrong but if anyone could let me know either how to pass the constant name into the UILable
or tell me that you cannot do this and I should try something different, that would be greatly appreciated.
How come you don't just do:
peopleLabel.text = peopleHondurasText

Swift - IBOutletCollection equivalent

I'm trying to replicate the Stanford Matchismo game from "Developing ios7 apps for iphone and ipad" in iTunesU in Swift.
On page 77 of the 3rd lecture slides, it shows using an IBOutletCollection which isn't an option on Swift. The Swift doc example shows one example that has an array of IBOutlet, but I can't figure out how to make Interface Builder connect multiple outlets to the same IBOutlet/IBOutlet Array.
Has anyone figured out how to do this yet?
I know that I can create 12 outlets and deal with it that way, but I'd like to make this work as closely as possible to the example in the lecture slides.
Update: This works properly in Xcode now - "Outlet Collection" is one of the connection options in Interface Builder, which creates something that looks like:
#IBOutlet var labelCollection: [UILabel]!
While we're waiting for a fix, you can approximate this using a computed property. Let's say my view has five UILabels that I want in a collection. I still have to declare each one, but then I also declare a computed property that collects them:
class MyViewController {
#IBOutlet var label1 : UILabel
#IBOutlet var label2 : UILabel
#IBOutlet var label3 : UILabel
#IBOutlet var label4 : UILabel
#IBOutlet var label5 : UILabel
var labels: UILabel![] { return [label1, label2, label3, label4, label5] }
Kind of annoying, but from then on we can treat the labels property as if it were an IBOutletCollection, and won't have to change the rest of our code once the bug is fixed:
override func viewDidLoad() {
super.viewDidLoad()
for (index, item) in enumerate(self.labels) {
item.text = "Label #\(index)"
}
}
Use:
#IBOutlet var lineFields: [UITextField]!
Then control-drag from UITextField elements to lineFields in order.
#IBOutlet var buttons : [UIView]!
then drag it from the connections inspector in the interface builder or whatever metod you usually use for that
EDIT
This was fixed in a later Beta release of Swift - there's now in
IBCollection option in the interface builder.
For early Beta releases of Swift:
I came across the same problem: in the release notes of Beta 2 you find the following statement:
Interface Builder does not support declaring outlet collections in Swift classes
I solved this the following way (easy to customize):
class CardGameViewController: UIViewController {
#lazy var cardButtons : UIButton[] = {
var tempBtn: UIButton[] = []
for v:AnyObject in self.view.subviews {
if v is UIButton {
tempBtn.append(v as UIButton)
}
}
return tempBtn
}()
...
Basically, it loops through all the subviews and checks if one is a UIButton. In that case it gets added to a temporary array. This temporary array is then used to lazy instantiate the cardButtons array. For all details, check: Matchismo: Objective-C to Swift
Follow steps to create an array of outlets and connect it with IB Elements:
Create an array of IBOutlets
Add multiple UIElements (Views) in your Storyboard ViewController interface (As shown in below snapshot)
Select ViewController (In storyboard) and open connection inspector
There is option 'Outlet Collections' in connection inspector (You will see an array of outlets there)
Connect if with your interface elements
-
class ViewController2: UIViewController {
#IBOutlet var collection:[UIView]!
override func viewDidLoad() {
super.viewDidLoad()
}
}
I got this working in Xcode seed 3 using this syntax
#IBOutlet strong var views: NSArray?
See my discussion here: https://stackoverflow.com/a/24686602/341994
What #machine said seems to be the current state (XCode 7.1) with iOS 9 bindings.
The key is to drag them all in order.
Use the first item to control+drag into the controller code and then change the Outlet type to collection. After the from the controller code file drag the outlet point onto each of the screen controls one by one in order (as #machine says)

Put checkmark in the left side of UITableViewCell

I want to make something similar to the WiFi settings page: when you tap the table cell, put a checkbox in the left side of the cell, and have a disclosure button accessory view on the right to show more details.
My question: is there a way to put the checkmark in the left side of a UITableViewCell without building a custom UITableViewCell ?
The answer is YES! You don't have to create a custom cell for that or add image views. To simulate checkboxes you only have to prepend a unicode symbol for a checkbox state to the cell text.
The unicode symbols I'm using for checkboxes are \u2705 for checked and \u2B1C for unchecked (nearly the same as \U0001F533 on iOS 5). iOS renders several unicode symbols as icons.
Here are some other symbols:
#"\u2611", #"\u2B1C", #"\u2705", #"\u26AB", #"\u26AA", #"\u2714", #"\U0001F44D", #"\U0001F44E"
Imitating the Wi-Fi settings page (with UITableViewCellStyleValue1):
cell.textLabel.text = #"\u2001 Wi-Fi 1";
cell.detailTextLabel.text = #"\U0001F512 \u268A";
cell.textLabel.text = #"\u2001 Wi-Fi 2";
cell.detailTextLabel.text = #"\U0001F512 \u268C";
cell.textLabel.text = #"\u2713 Wi-Fi 3";
cell.detailTextLabel.text = #"\U0001F513 \u2630";
Of course you can do that :) For example use UITableViewCellStyle
UITableViewCellStyleDefault,
// Simple cell with text label and optional image view
//(behavior of UITableViewCell in iPhoneOS 2.x)
...and put a custom "checkmark" image in that "optional image view".
In Swift you can press Command + Ctrl + Spacebar to show emoticons and special characters or you can copy this ✓ ✔️
So here is a way to do it. Place a UIImageView on the left edge of the cell. Do not set an image so it's blank as a start.
Then, assuming you are doing a custom cell and a class for that UITableViewCell, do something like this:
import UIKit
class MyCustoTableViewCell: UITableViewCell {
#IBOutlet weak var commandOption: UILabel!
#IBOutlet weak var checkMarkImage: UIImageView!
// Sets a left side checkmark on a cell when selected
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.checkMarkImage.image = selected ? UIImage(named: "BlueCheck") : .None
self.commandOption.font = selected ? UIFont(name: "Avenir-Heavy", size: 22) : UIFont(name: "Avenir-Book", size: 22)
// more check mark on the right side
// self.accessoryType = selected ? .Checkmark : .None
}
}
Note the bottom commented out is if you want the checkmark on the right. The super.setSelecdted method fires when a the user has tapped on the table cell. Be sure you have this implemented also:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// do any changes needed here or leave blank if just adding check
}
}
Finally, make sure you have #2x and #3x check mark PNGs assets in your Assets folder. Note those are called "BlueCheck" in this example but use what ever your check mark assets are called.
This approach makes sure that when a user selects a new row the check mark goes away on the current cell.
In this case I'm bolding the text that is to the right of the image view to further indicate it was the selected row.
No. You have to create a custom cell for that.
You should look at the UITableViewCell reference and Table View Guide. Use standard cell style with editingAccessoryType property set to UITableViewCellAccessoryCheckmark.