Swift displaying passed data from prepareForSegue - xcode

Ok, first time I have ever had to ask for help on here, usually I can search and find my answer but not this time. I have a table that displays a pictures and names. If one of them are selected it goes to another view and the data passes. However, I am trying to get that passed information to display in a table like: Name: (Passed Info), Age: (Passed Info), Gender: (Passed Info) etc. I know the data passes because I can display the info in a label, but I can not figure out how to get it to show in a table. Index issue? String Issue?
This is code that passes the info:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var detailsViewController: DetailsViewController = segue.destinationViewController as DetailsViewController
var horseIndex = appsTableView!.indexPathForSelectedRow()!.row
var selectedHorse = self.horses[horseIndex]
detailsViewController.horse = selectedHorse
This is the code on the controller getting the data where I want the table to display
class DetailsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
#IBOutlet weak var titleLabel: UILabel!
var horse: Herd?
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.horse.count;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
cell.textLabel?.text = self.horse[indexPath.Row]
return cell
}
}
I get an error on the return self.horse.count stating not a member and an error on the self.horse[indexPath.Row] stating NSIndex does not have a member named row.
I feel like I am not unwrapping it properly or something, but I can not figure it out or find an answer in my searches. If you need more information please let me know and thanks in advance for any help.

CLARIFYING...
You correctly grab self.horses[horseIndex] in the segue method, so you've already done the work to get the 1 specific horse. No need to do it again. Is self.horses tied to the Herd type? Confusing why Herd shows up again in the DetailView - you don't seem to need it.
It sounds like what you actually want at this point is a tabular layout of the details of that single horse ... Correct? Like going from your entire "Contacts" list to the tabular view of a single contact?
That no longer involves the array of multiple horses, so your use of Herd? and herd.count aren't necessary. Use static labels or a static tableView to show the info from the 1 Horse.
WHAT'S YOUR DATA STRUCTURE & WHAT GOES IN DETAIL VIEW?
Presumably what you want to create (if you haven't already) is a Horse Type:
class Horse {
//you could use optional properties here, or require them for a fully initialized Horse.
let name:String?
let gender:String?
let breed:String?
var age:Int?
var restingHeartRate:Int?
init(name:String?, gender:String?, breed:String?, age:Int?, restingHeartRate:Int?) {
//set arguments passed in to respective properties of self...
}
func winTripleCrown() {
println("\(name!) Wins! Suck on that, Sea Biscuit!")
}
}
Ensure your horses array is declared to only take Horse instances, and fill it appropriately:
var horses = [Horse]()
let horse1 = Horse(name:"Bob" gender:"male" breed: "Yessir, I am a real horse" age:42 restingHeartRate: 30)
horses.append(horse1)
println(horses.count) //prints "1"
horses[0].winTripleCrown() //prints: "Bob Wins! Suck on that, Sea Biscuit!"
Use the Horse Type instead of Herd in the DetailViewController:
var horse: Horse? //nil until set in prepareForSegue
Then in the segue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
...
detailsViewController.horse = selectedHorse
Now, assuming you only put properly created Horse instances into the initial array, and there IS one at the selectedIndex, you're guaranteed that the horse variable in DetailViewController is a Horse instance, and that it's the one selected in the 1st "overall" tableView.
CREATE THE DETAIL VIEW
EASY:
The easy solution at this point is to create a detailView layout with labels, images, etc and hook them up to #properties in the DetailViewController. Then map the Horse properties to the #IBOutlets. Done. No need to mess w/tableViews anymore - just fake it or use a scrollView to make it look like a table.
TABLEVIEW ROUTE
But if you want to use a UITableView, then you'd need the delegate methods like you're using... The difference is you need to look at the # of properties of the single Horse instance - not anything about the overall list of Horses in a Herd or total array.
You COULD hard-code the # of rows if you're certain the # and order of Horse properties will always be consistent:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5 //1 row per property in a `Horse` instance as defined above
}
However, if you want to account for more dynamic results (like an array of wins/losses, or a photo gallery of variable length) you'd need to have enough cells to map all the Horse properties. How can you do that with a custom type that isn't an array or dictionary?
In Swift, you can determine how many properties a Type has by creating a "Mirror" type and using it for introspection, to reflect info back to you like the count property missing above.
let horseMirror = reflect(self.horse)
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.horseMirror.count //should also return "5" but no longer hard-coded
}
And then in cellForRowAtIndexPath: you can switch on the indexPath to assign the various Horse properties to the labels (or images, etc) in the tableView cells. Create custom tableView cell types w/unique identifiers if you want to show different information, or stick w/built-in options:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
//haven't played w/this to see if the optionals are correct. Consider it psuedocode...
switch indexPath.row {
case 1: cell.textLabel.text = self.horse.name?
case 2: cell.textLabel.text = self.horse.gender?
case 3: cell.textLabel.text = self.horse.breed?
case 4: cell.textLabel.text = String(self.horse.age?)
case 5: cell.textLabel.text = String(self.horse.restingHeartRate?)
default: cell.textLabel.text = ""
}
return cell
}

Related

How to get the right row in a #IBsegueAction function?

I can segue from an embedded UIKit tableview to a SwiftUI view, with the necessary data. I select the indexPath.row with a tableView(_didSelectRowAt).
However, the #IBSegueAction takes place before the didSelectRowAt. This makes the detailView lag one selected row: it shows the previously selected row.
I tried to put the didSelectRowAt first, tried to embed them: no chance
I saw in a WWDC video that it should be possible lo select the right row, but can't figure out the right syntax from this short segment (about minute 6:00)
https://developer.apple.com/videos/play/wwdc2019/231/
#IBSegueAction func MeasurementDetail(_ coder: NSCoder) -> UIViewController? {
return UIHostingController(coder: coder, rootView: PointDetailSwiftUIView(pointDetail: measurements[selectedMeasurementRow]))
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedMeasurementRow = indexPath.row
}
How do I solve the problem?
In that WWDC session, Tanu doesn't care when UIKit calls tableView(_:didSelectRowAt:). Instead, her #IBSegueAction asks the table view for the selected row, by using tableView.indexPathForSelectedRow.
Make sure you have a tableView outlet connected to your table view. You already have a tableView outlet if your view controller is a subclass of UITableViewController. Then use tableView.indexPathForSelectedRow:
#IBSegueAction func MeasurementDetail(_ coder: NSCoder) -> UIViewController? {
guard let row = tableView.indexPathForSelectedRow?.row else { return nil }
let detailView = PointDetailSwiftUIView(pointDetail: measurements[row])
return UIHostingController(coder: coder, rootView: detailView)
}

Should a View contain the Model it present?

In MVC pattern, should a View contain the Model it is presenting?
For example, I have an Item model, ItemsListView & ItemRowView to display it.
In order to display the list, should I pass the Item model to ItemRowView (1) or I can pass Item.title, Item.details, Item.image, etc to the ItemRowView (2)
In (1), it is violate the MVC design pattern where View does not talk directly to the Model, and the View can also call Model methods
In (2), when make the View dummy, If in another ViewController, we use the ItemsList a gain, we have to duplicate the passing parameters again, and what happend if we need to pass one more properties to the user?
There another way, using ViewModel to wrap around the Model to handle UI Logic, but that will create many classes, and everytime I get a list of models, I also have to map it to ViewModels
example:
class ItemModel {
var name: String = ""
var price: Double = 9000
}
Should I assign the model to UITableViewCell like this: (so the cell can update the data itself whenever new data come)
class ItemTableViewCell: UITableViewCell {
#IBOutlet var titleLabel: UILabel!
var item: ItemModel! {
didSet {
self.updateUI()
}
}
func updateUI() {
titleLabel.text = item.name
}
}
Or should I keep the cell dummy, and do the update UI in ViewController:
class ItemTableViewCell: UITableViewCell {
#IBOutlet var titleLabel: UILabel!
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: ItemTableViewCell = tableView.dequeueReusableCell(withIdentifier: "ItemTableViewCell", for: indexPath)
var item = items[indexPath.row]
cell.titleLabel.text = item.name
return cell
}
Yes. As it enables you to use the base methods of the model itself instead of creating wrapper classes and handling conversion based on design change.
The Rule of thumb nowadays is to have a view-model for each view if that view requires more than one object.This allows you to handle design and data changes without touching the controller.

Swift 2 Table View Cell with Hyperlink

I've created a UITableView with different Sections and cells like: "Follow us on Instagram" or "Like us on Facebook".
This cells should have a link to each page.
I tried this:
#IBAction func WebLink(sender: AnyObject) {
if let url = NSURL(string: "http://...") {
UIApplication.sharedApplication().openURL(url)
}
}
But i can't link the #IBAction with the Cell...
It should look like this and on every Cell should be a Hyperlink.
You can directly use:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as UITableViewCell!
// launch your func WebLink with the currentCell
...
}
I'm not finding anything that would allow us to do so with a simple label. But there is a library available that does so in github. It seems you can't do so with a simple label in Swift. However, there is a library called TTTAttributedLabel that I think does what you're looking for. Here's a link to the library: https://github.com/TTTAttributedLabel/TTTAttributedLabel
#IBOutlet var exampleLabel: TTTAttributedLabel!
//...
let exampleString: NSString = "My super cool link"
exampleLabel.delegate = self
exampleLabel.text = exampleString as String
var range : NSRange = exampleString.rangeOfString("link")
exampleLabel.addLinkToURL(NSURL(string: "http://www.stackoverflow.com")!, withRange: range)
func attributedLabel(label: TTTAttributedLabel!, didSelectLinkWithURL url: NSURL!) {
UIApplication.sharedApplication().openURL(url)
}
Side note: Did not ever imagine I'd miss Objective-C. I'll update with a Swift 3.0 answer if this is what you were looking for.

I have a bug with a Swift table view cell action

I'm using editActionsForRowAtIndexPath with Swift to add a option to my app cells.
My app is a homework list app, where you can set its subject, its category, the date you need it ready for and add a description. Also, there is a option that when you swipe the cell to the left you will be able to set the homework as Done. To show that, I created a label and added it to the cell, that if the homework is pending, the label will be Pending. If it was set as done, it will be Concluded. The thing is that when I'm setting ONE cell as done, it is setting all the other cells, which is not what I want.
The code I'm using to say that I only want THIS cell to be set as done is this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("AtivCell") as? AtivCell {
if !isPending {
pendencia[indexPath.row] = false
saveData()
}
cell.configureCell(materias[indexPath.row], data: datas[indexPath.row], descricao: descricoes[indexPath.row], pendencia: pendencia[indexPath.row], categoria: categorias[indexPath.row])
return cell
} else {
return AtivCell()
}
}
Here is the entire project, I really need help, I don't think I did anything wrong.
I've tried deleting the app from my iPhone (where I'm testing), tried closing Xcode, tried everything, it just does not work!!!
Link: https://github.com/HenriqueDoura/Agenda/tree/master/agenda-app
You should be using dequeueReusableCellWithIdentifier with the forIndexpath parameter, which always returns a cell. The one you've used requires you to test if it's nil and create one, which you are doing in the else clause and then not initialising it.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("AtivCell", forIndexPath: indexPath) as? AtivCell {
cell.configureCell(materias[indexPath.row], data: datas[indexPath.row], descricao: descricoes[indexPath.row], pendencia: pendencia[indexPath.row], categoria: categorias[indexPath.row])
return cell
}
You can use your current function if you want, but you need to declare cell as var, and create it if dequeue returns nil, and then set its values.
Also your !isPending code will not work properly. Within editActionsForRowAtIndexPath, you should be setting pendencia[indexPath.row] = false.

Provide tableviewcell with var of an object stored in a array

i want to do the following thing:
1- Create objects from a class (with 3 variables)
2- Store those objects in a array
3- Create a tableview
4- The cell from the tableview must provide input from the first variable.
I got stuck on the last step. Can anybody help me?
var totalBooks = [AnyObject]() *// create an empty array*
var newBook = Book(setTitle: "booktitle", setWriter: "writer", setFile: "file") *// creates object from a class with 3 variables*
totalBooks.append(newBook) *// puts the object in the array*
*cell.textLabel?.text = totalBooks **???*** *// must put the first variable (setTitle) as output information.*
Thanks in advance
1. You should create the array like this:
let totalBooks = [Book]()
2. setTitle is not a good name of a property - use title
3. Set your totalBooks array as a data source for your table view by conforming to the UITableViewDataSource Protocol.
You can read about it here and try it yourself - UITableViewDataSource
4. In tableView(_:cellForRowAtIndexPath:) set the cell's title label for the corresponding item in your totalBooks data source.
Here is a solution.
Create a property in your view controller var myBooks = [Book]()
In viewDidLoad method
override func viewDidLoad() {
super.viewDidLoad()
for _ in 1...3 {
var newBook = Book(setTitle: "booktitle", setWriter: "writer", setFile: "file")
myBooks.append(newBook)
}
}
In cellForRowAtIndexPath delegate method
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
let book = self.myBooks[indexPath.row]
cell.label.text = book.title //or book.setTitle, whatever you have
return cell
}

Resources