Multiple RowTypes in TableView - watchKit - ios

It's rather easy to create a simple TableView with one row type.
You just set
tableView.setNumberOfRows(yourArray.count, withRowType: "yourowtype")
and then add a for loop to fill up your uilabel or whatever you have with data from the array.
When it comes to multiple row types, it's not so clear. I'm aware you have to set
tableView.setRowTypes(yourRowTypesArray)
but i don't understand the rest.
In iOS you have a very clear and straightforward indexPath.row solution in the cellForRowAtIndexPath, where you can say - Okay, i want this array to fill those indexPaths, the other array should fill those e.t.c. with simple IF conditional.
In WatchKit, however, there is no such thing as indexPath.row and it's not clear to me how you can assign specific row numbers for a specific array ? And why should you remove setNumberOfRows (as i've seen in the examples all over the net) in a multiple row type solution?
I've browsed the net heavily regarding the issue and i haven't been able to find a decent workable solution. Just tricks and workarounds.
Thank you.
UPDATE: Adding codes
My arrays
var questionsList = [["[What is the color of?]"],["Which city is the capital of Great Britain", "additional info"],["Some question"]]
var answersList1 = [["Blue"],["London"],["some answer 1"]]
var answersList2 = [["Grey"],["Liverpool"],["some answer 2"]]
The loadTable function
private func loadTable(){
tableView.setRowTypes(rowTypes)
for i in 0 ..< questionsList[0].count {
let rowController = tableView.rowControllerAtIndex(i) as! TableViewRowController
rowController.lblQuestion.setText(questionsList[0][i])
}
let rowController1 = tableView.rowControllerAtIndex(answersList1[0].count) as! AnswerRowController1
rowController1.button1.setTitle(answersList1[0][0])
let rowController2 = tableView.rowControllerAtIndex(answersList2[0].count+1) as! AnswerRowController2
rowController2.button2.setTitle(answersList2[0][0])
}

I would rather suggest you to refine your model. It looks really difficult to understand. Refactor it into class or struct to make it easy to understand.
Here is my approach to refactor it a bit and create a sort of thing that you wanted,
let QuestionRowIdentifier = "QuestionRowIdentifier"
let AnswerRowIdentifier = "AnswerRowIdentifier"
let QuestionSeparatorRowIdentifier = "QuestionSeparatorIdentifier"
protocol QuestionAnswerRowTypes {
var titleLabel: WKInterfaceLabel! { get set }
}
class QuestionRowController: NSObject, QuestionAnswerRowTypes {
#IBOutlet var titleLabel: WKInterfaceLabel!
}
class AnswerRowController: NSObject, QuestionAnswerRowTypes {
#IBOutlet var titleLabel: WKInterfaceLabel!
}
struct Question {
let title: String
let additionalInfo: String?
let answers: [String]
}
let questions = [
Question(title: "What is the color of?", additionalInfo: nil, answers: [
"Blue",
"Gray"
]),
Question(title: "Which city is the capital of Great Britain?", additionalInfo: "additional info", answers: [
"London",
"Liverpool"
]),
Question(title: "Some question?", additionalInfo: nil, answers: [
"some answer 1",
"some answer 2"
])
]
class InterfaceController: WKInterfaceController {
#IBOutlet private var tableView: WKInterfaceTable!
var names = ["Alexander", "Ferdinand", "Jack", "Samuel", "Thompson", "Tony"]
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let rowTypes = getRowTypes()
tableView.setRowTypes(rowTypes)
for i in 0 ..< rowTypes.count {
if let rowController = tableView.rowControllerAtIndex(i) as? QuestionAnswerRowTypes {
rowController.titleLabel.setText(textAtIndex(i)!)
}
}
}
func getRowTypes() -> [String] {
return questions.flatMap { question in
return [
[QuestionRowIdentifier],
Array(count: question.answers.count, repeatedValue: AnswerRowIdentifier),
[QuestionSeparatorRowIdentifier]
].flatMap { $0 }
}
}
func textAtIndex(index: Int) -> String? {
let titles = questions.flatMap { question in
return
[
[Optional.Some(question.title)],
question.answers.map(Optional.Some),
[Optional.None],
]
}.flatMap( { $0 })
return titles[index]
}
}
And here is the end result,

Related

For-in loop requires '[UserVehicles]?' to conform to 'Sequence'; did you mean to unwrap optional? Swift

I have a data model which I made for API returns, it is something like this:
struct VehicleData: Codable {
let _embedded: Embedded
}
struct Embedded: Codable {
let userVehicles: [UserVehicles]
}
struct UserVehicles: Codable {
let id: String
let images: [String]
let userId: String
let vehicle: Vehicle
let originalPrice: OriginalPrice
let hasBasicInsurance: Bool
}
I have used callback function to pass it to my ViewController, now I want to get check in the useVehiclers list, how many vehicles hasBasicInsurance. basically, vehicleList?._embedded.userVehicles[i] = true
this is my function code to use the vehicle data in ViewController:
var vehicleManager = VehicleManager()
var vehicleList: VehicleData?
var i: Int = 0
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
vehicleManager.retrieveUserVehicle()
vehicleManager.onDataUpdate = { [weak self] (data: VehicleData) in
self?.useData(data: data)
}
tableView.dataSource = self
tableView.delegate = self
tableView.tableFooterView = UIView() //remove empty tableView cells
tableView.register(UINib(nibName: Constants.vehicleListCellNibName, bundle: nil), forCellReuseIdentifier: Constants.vehicleListToBeInsuredIdentifier)
}
func useData(data: VehicleData) {
vehicleList = data
// code below has issues....
for i in [vehicleList?._embedded.userVehicles] {
if let vechile = vehicleList?._embedded.userVehicles[i].hasBasicInsurance {
if vehicle == true {
i = i + 1
print(">>number of of insured vehidle: \(i)")
} else {
print(">>>number of of insured vehidle: \(i)")
}
}
}
}
Do you know how to fix it?
You need to supply a default value for optional as a good practise instead of force unwrap
for i in vehicleList?._embedded.userVehicles ?? [] { }
It's not clear from your code, but it looks like vehicleList is optional. It probably should not be (see Leo Dabus's comments). It is rare that it makes sense to have an optional array. That suggests there's some difference between an empty array and a missing array. If there is, then that's fine, but in most cases you should just use a non-optional array and make it empty.
Whether you fix that or not, the solution to this particular problem is to just use a non-optional value, and you have one: data. So change the loop to:
for i in data._embedded.userVehicles { ... }
From your updated question, you note "I want to get check in the useVehiclers list, how many vehicles hasBasicInsurance." It seems you want to put that value in i. If so, that would be:
func useData(data: VehicleData) {
vehicleList = data
i = data._embedded.userVehicles
.filter(\.hasBasicInsurance)
.count
}
You can also use for_each loop for this, for eg like this:
vehicleList?._embedded.userVehicles.for_each(x in /*Do your thing here*/)

Value of type '[UILabel]' has no member 'numberOfLines'

I'm using Swift.
The text for my descriptions are being cut off. I would like all of the text to be showing, but when I add numberOfLines = 0 I get an error.
"Value of type '[UILabel]' has no member 'numberOfLines'"
Not really sure what I'm doing wrong.
class SurveyResultsViewController: UITableViewController {
#IBOutlet var lblSortedScores : [UILabel]!
#IBOutlet var sortedTitle : [UILabel]!
#IBOutlet var sortedDescription : [UILabel]! {
didSet {
sortedDescription.numberOfLines = 0
}
}
#IBOutlet weak var cellFinishButton : UITableViewCell!
var survey: LikertSurvey?
var points = [0, 0, 0, 0]
var results: [(Int, Int)] = []
var descriptionLabels =
[("Money Avoiders think that money is bad and that rich people are greedy. They often feel like they don't deserve to have money.\n\nAvoiders may have a hard time sticking to a budget, can be compulsive buyers at times, and would rather not look at their bank statements."),
("Money Worshippers believe that money will make them happier and solve their problems, but they will never have enough of it.\n\nWorshippers are likely to overspend or have credit card debt. They also may overwork themselves at the expense of close relationships. "),
("People with the Money Status belief see money as a way of achieving higher status. They can believe their self worth is tied to their net worth.\n\nThose may lie about money, pretend to be richer than they are, and take on risks to make money quickly. "),
("Those with the Money Viligance belief believe in saving money. They can often be anxious and secretive about their finances.\n\nThey may be overly wary of their finances and scared to buy anything on credit.")]
override func viewDidLoad() {
super.viewDidLoad()
UserDefaults.standard.setValue(points, forKey: "points")
self.setResults()
self.initUI()
}
private func setResults() {
for (index, point) in points.enumerated() {
results.append((index, point))
}
results.sort { (result1, result2) -> Bool in
return result1.1 > result2.1
}
}
private func initUI() {
for i in 0 ..< results.count {
let title = survey!.questionCategories[results[i].0]
let description = descriptionLabels[results[i].0]
lblSortedScores[i].text = "\(results[i].1) points"
sortedTitle[i].text = "\(title)"
sortedDescription[i].text = "\(description)"
}
let finishButtonTap = UITapGestureRecognizer(target: self, action: #selector(self.finishButtonTapped(_:)))
cellFinishButton.addGestureRecognizer(finishButtonTap)
self.navigationItem.hidesBackButton = false
}
#objc func finishButtonTapped(_ sender: UITapGestureRecognizer) {
self.survey?.completed = true
let alertController = UIAlertController(title: "Congratulations! You earned 100 XP from completing this quest!", message: "", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Ok",
style: .default) { action in
self.performSegue(withIdentifier: "unwindToSectionTableView", sender: self)
})
self.present(alertController, animated: true, completion: nil)
}
}
If it's because the UILabel is in brackets how can I work around this? When I remove them, I just get more errors.
The variable sortedDescription is of type [UILabel] - an array of UILabel's. The UILabel class has the property numberOfLines and not the Array class.
If you want to set the numberOfLines for each UILabel in the sortedDescription array, try something like this:
var sortedDescription: [UILabel]! {
didSet {
for label in sortedDescription {
label.numberOfLines = 0
}
}
}
I copy your code and run, i explore some errors. First
#IBOutlet var sortedDescription : [UILabel]! {
didSet {
sortedDescription.numberOfLines = 0
}
}
Array of UILabel don't have numberOfLines. You should change to this
#IBOutlet var sortedDescription : [UILabel]! {
didSet {
sortedDescription.forEach({ (label) in
label.numberOfLines = 0
})
}
}

I am running into an Index out of Range Error in Swift 5

I am new to swift programming and I am running into an error on the bolded piece of code below, this is my first post ever on Stack Overflow and am trying to figure out how to fix this Index out of Range Error. Any help would be greatly awesome!
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var progressBar: UIProgressView!
#IBOutlet weak var trueButton: UIButton!
#IBOutlet weak var falseButton: UIButton!
let quiz = [
["Four + Two is equal to Six.", "True"],
["Five - Three is greater than One", "True"],
["Three + Eight is less than Ten, False"]
]
var questionNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
}
#IBAction func answeredButtonPressed(_ sender: UIButton) {
let userAnswer = sender.currentTitle // True, False
**let actualAnswer = quiz[questionNumber][1]**
if userAnswer == actualAnswer {
print("Right!")
} else {
print("Wrong")
}
if questionNumber + 1 < quiz.count {
questionNumber += 1
} else {
questionNumber = 0
}
updateUI()
}
func updateUI() {
questionLabel.text = quiz[questionNumber][0]
}
}
Welcome to Stackoverflow! The reason you get the crash error index out of range when you attempt to extract the boolean string is because the element of your index 2 has a single string.
["Three + Eight is less than Ten, False"]
Simply put the " in there.
["Three + Eight is less than Ten", "False"]
One more approach you can consider is to use Tuple or Dictionary.
If you use Tuple, and you commit the same mistake, it will give you a compile-time error, like:
Heterogeneous collection literal could only be inferred to '[Any]';
add explicit type annotation if this is intentional
Tuple example:
let quiz = [
("Four + Two is equal to Six.", "True"),
("Five - Three is greater than One", "True"),
("Three + Eight is less than Ten", "False")
]
let answer = quiz[2].1
As suggested by Paulw, a better way would be making a model. Like:
Quiz.swift
struct Quiz {
/// Contains the question string.
var question: String
/// Consider changing this to `Bool`.
var answer: String
}
Usage:
let quiz = [
Quiz(question: "Four + Two is equal to Six.", answer: "True"),
Quiz(question: "Five - Three is greater than One", answer: "True"),
Quiz(question: "Three + Eight is less than Ten", answer: "False")
]
let answer = quiz[2].answer

Set up WatchKit Table

I'm trying to load my data into a WatchKit table. Basically, set the text of the match label in each table group cell with the array of matchs I have.
I've got the data, and everything set up, but actually loading it into the table is where I'm stuck.
InterfaceController.swift:
var receivedData = Array<Dictionary<String, String>>()
var eventsListSO = Array<Event>()
#IBOutlet var rowTable: WKInterfaceTable!
func doTable() {
eventsListSO = Event.eventsListFromValues(receivedData)
rowTable.setNumberOfRows(eventsListSO.count, withRowType: "rows")
for var i = 0; i < self.rowTable.numberOfRows; i++ {
let row = rowTable.rowControllerAtIndex(i) as? TableRowController
for eventm in eventsListSO {
row!.mLabel.setText(eventm.eventMatch)
NSLog("SetupTableM: %#", eventm.eventMatch)
}
}
}
I was trying to do it in doTable because that seemed like best place to do this, and I think doTable is set up right, but I'm not sure? Not sure if I need to make the array an optional type or what.
Here is the referencing code if needed:
RowController.swift:
class TableRowController {
#IBOutlet var mLabel: WKInterfaceLabel!
#IBOutlet var cGroup: WKInterfaceGroup!
}
Event.swift:
class Event {
var eventTColor:String
var eventMatch:String
init(dataDictionary:Dictionary<String,String>) {
eventTColor = dataDictionary["TColor"]!
eventMatch = dataDictionary["Match"]!
}
class func newEvent(dataDictionary:Dictionary<String,String>) -> Event {
return Event(dataDictionary: dataDictionary)
}
class func eventsListFromValues(values: Array<Dictionary<String, String>>) -> Array<Event> {
var array = Array<Event>()
for eventValues in values {
let event = Event(dataDictionary: eventValues)
array.append(event)
}
return array
}
}
So I'm not sure if:
- doTable is set up right (can't be because eventsListSO.count is null)
The way you work with tables in WatchKit is a lot different than UIKit.
After you call setNumberOfRows you need to iterate over each row and get the RowController.
for var i = 0; i < self.rowTable.numberOfRows; i++ {
var row = self.rowTable.rowControllerAtIndex(i)
//setup row here
}
You can check Raywenderlich's tutorial about WatchKit: http://www.raywenderlich.com/96741/watchkit-tutorial-with-swift-tables-glances-and-handoff, it teach you how to show tables on your watch, hope this help!

Map Object into 2D Array Swift for TableView Sections

I could not figure out a better way of doing this. I am mapping all the properties of the Student Object into a 2D Array. So my TV has sections.
I cannot use a Static Tableview either, if so this problem would not exist.
So my code in the TVC
let currentUser = PFUser.currentUser()! as! MyUser
var membershipSection:[[String:String]]!
var detailsSection:[[String:String]]!
var emergancySection:[[String:String]]!
var medicalSection:[[String:String]]!
var titlesForSection = ["MEMBERSHIP", "DETAILS", "EMERGANCY CONTACT", "MEDICAL HISTORY"]
var combo = [[[String:String]]]() // Data Source for TableView
// The following is called from ViewDidLoad
func loadDisplayDataSource() {
combo.removeAll(keepCapacity: true)
var idString = "Awaiting ID Generation"
if student.objectId != nil {
idString = student.objectId!
}
membershipSection = [["Sessions":student.sessionsRemaining], ["Details":""], ["ID":idString]]
detailsSection = [["First Name":student.firstName], ["Last Name":student.lastName], ["DOB":""], ["Address":""], ["Phone":""], ["Email":student.email], ["Occupation":""]]
emergancySection = [["Name":""], ["Phone":""]]
medicalSection = [["Recent Surgery":""], ["Hypertension":""], ["Diabetes":""], ["Caradic":""], ["Epilesy":""], ["Syncope":""], ["Medications":""], ["Medical Details":""], ["Other Injuries":""]]
combo.append(membershipSection)
combo.append(detailsSection)
combo.append(emergancySection)
combo.append(medicalSection)
self.tableView.beginUpdates()
var range = NSMakeRange(0, self.numberOfSectionsInTableView(self.tableView))
var sections = NSIndexSet(indexesInRange: range)
self.tableView.deleteSections(sections, withRowAnimation: UITableViewRowAnimation.None)
self.tableView.insertSections(sections, withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
}
Is there a better way to map a object's data into sections ? The way I'm doing it works, but is a little confusing. If i could use a static view this would be easier, but I cannot as using a drop in TV within a Normal VC and you cannot use static TV in these. Which is annoying! Is there a cleaner way?
Can I make this more SWIFTY - A better way to create my combo data source.
Thanks for any advice.
My end result - which is working looks like this - A TVC with sections.
I'm not entirely sure what you're asking. What is 'combo' used for?
If you want to just package up your data in a cleaner fashion, structs in Swift are nice for this. Something like:
struct EmergencySection{
var name: String!
var phone: String!
}
//then to instantiate in loadDisplayDataSource
var emergencySection = EmergencySection(name: "", phone: "")
combo.append(emergencySection)
Try using RETableViewManager, it's pretty awesome for such tasks. Well, it's fully Objective-C, but at least you could have a quick look of it.
How about this?
import UIKit
class
ViewController: UITableViewController {
var combo = [ [ String: AnyObject? ] ]()
let titlesForSection = ["MEMBERSHIP", "DETAILS", "EMERGANCY CONTACT", "MEDICAL HISTORY"]
override func
viewDidLoad() {
super.viewDidLoad()
// Something about studen data
combo = [
[ "Sessions":"student.sessionsRemaining", "Details":"", "ID":"idString" ]
, [ "First Name":"student.firstName", "Last Name":"student.lastName", "DOB":"", "Address":"", "Phone":"", "Email":"student.email", "Occupation":"" ]
, [ "Name":"", "Phone":"" ]
, [ "Recent Surgery":"", "Hypertension":"", "Diabetes":"", "Caradic":"", "Epilesy":"", "Syncope":"", "Medications":"", "Medical Details":"", "Other Injuries":"" ]
]
}
override func
numberOfSectionsInTableView(tableView: UITableView ) -> Int {
return combo.count
}
override func
tableView(tableView: UITableView, numberOfRowsInSection section: Int ) -> Int {
return combo[ section ].count
}
override func
tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return titlesForSection[ section ]
}
override func tableView( tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath ) -> UITableViewCell {
let v = tableView.dequeueReusableCellWithIdentifier( "SomeIdentifier" ) as! UITableViewCell
let w = combo[ indexPath.section ]
let wKey = Array( w.keys )[ indexPath.row ]
v.textLabel!.text = wKey
v.detailTextLabel!.text = w[ wKey ] as? String
return v
}
}
Here is how I am doing it, a bit cleaner
private struct Details {
static let title = "DETAILS"
static let firstName = (key:"First Name", index:0)
static let lastName = (key:"Last Name", index:1)
static let dob = (key:"DOB", index:2)
static let address = (key:"Address", index:3)
static let phone = (key:"Phone", index:4)
static let email = (key:"Email", index:5)
static let occupation = (key:"Occupation", index:6)
static func splitIntoDictionaries(student: Student) -> [[String:String]] {
return [
[firstName.key:student.firstName], // 0
[lastName.key:student.lastName], // 1
[dob.key:""],
[address.key:""],
[phone.key:""],
[email.key:student.email],
[occupation.key:""]
]
}
}
You can use a UITableViewController with static cells in a normal UIViewController by adding it as a child view controller.
parentVC.addChildViewController(childVC)
childVC.view.frame = parentVC.view.bounds
parentVC.view.addSubview(childVC.view)
childVC.didMoveToParentViewController(parentVC)

Resources