again me with a short question since Swift is confusing me ATM - but I hope I will get used to it soon ;)
Ok so what I was wondering is: When I call a TableView and generate different Cells is there a way to Interrupt after a few and wait for User Input and react to it?
For Example: 2nd Cell is something like "Go to North or West" after that I want a User Input - with Buttons - in whatever direction he likes to go and react to it with following Cells (different Story Parts -> out of other Arrays?).
What is confusing me is that I just load the whole Application in viewDidLoad and i don't know how I can control the "Flow" within this.
I would really appreciate if someone could show me how I can achieve this maybe even with a small description about how I can control the Program Flow within the Method. I really think this knowledge and understanding would lift my understanding for Swift a few Levels higher.
Thanks in advance!
Here is my current Code which is not including any functionality for the named Question since I don't know how to manage this :)
import UIKit
class ViewController: UIViewController {
var storyLines = ["Test Cell 1 and so on first cell of many","second cell Go to North or West","third Cell - Buttons?"]
var actualTables = 1
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView.dataSource = self
}
#IBOutlet weak var tableView: UITableView!
}
extension ViewController : UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return storyLines.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TxtLine", for: indexPath)
cell.textLabel?.text = storyLines[indexPath.row]
cell.textLabel?.numberOfLines = 0;
cell.textLabel?.lineBreakMode = .byWordWrapping
return cell
}
}
Cocoa is event driven. You are always just waiting for user input. It's a matter of implementing the methods that Cocoa has configured to tell you about it.
So here, for example, if you want to hear about when the user presses a button, you configure the button with an action-and-target to call a method in your view controller when the user presses it. That way, your method can see which button the user pressed and remake the table view's data model (your storyLines array) and reload the table with the new data.
Related
Problem I want to allow users to hit 'swap' in a table cell and then find a different Realm object to populate the 2 text labels (for exercise name and number of reps) in the cell with the values from the new object.
Research There's quite a bit (admittedly old) on 'moving rows' (e.g. here How to swap two custom cells with one another in tableview?) and also here (UITableView swap cells) and then there's obviously a lot on reloading data in itself but I can't find anything on this use case.
What have I tried my code below works fine for retrieving a new object. i.e. there's some data in the cell, then when you hit the 'swapButton' it goes grabs another one ready to put in the tableView. I know how to reload data generally but not within one particular cell in situ (the cell that the particular swap button belongs to... each cell has a 'swap button').
I'm guessing I need to somehow find the indexRow of the 'swapButton' and then access the cell properties of that particular cell but not sure where to start (I've played around with quite a few different variants but I'm just guessing so it's not working!)
class WorkoutCell : UITableViewCell {
#IBOutlet weak var exerciseName: UILabel!
#IBOutlet weak var repsNumber: UILabel!
#IBAction func swapButtonPressed(_ sender: Any) {
swapExercise()
}
func swapExercise() {
let realmExercisePool = realm.objects(ExerciseGeneratorObject.self)
func generateExercise() -> WorkoutExercise {
let index = Int(arc4random_uniform(UInt32(realmExercisePool.count)))
return realmExercisePool[index].generateExercise()
}
}
//do something here like cell.workoutName
//= swapExercise[indexRow].generateExercise().name???
}
Hold your objects somewhere in a VC that shows UITableView. Then add the VC as the target to swap button. Implement swapping objects on button press and reload data of table view after.
The whole idea is to move logic to view controller, not in separate cell.
There are 2 ways.
1. Adding VS as button action target.
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ... // get cell and configure it
cell.swapBtn.addTarget(self, action: #selector(swapTapped(_:)), for: .touchUpInside)
return cell
}
func swapTapped(_ button: UIButton) {
let buttonPosition = button.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(buttonPosition)!
// find object at that index path
// swap it with another
self.tableView.reloadData()
}
Make VC to be delegate of cell. More code. Here you create protocol in cell and add delegate variable. Then when you create cell you assign to VC as delegate for cell:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ... // get cell and configure it
cell.delegate = self
return cell
}
func swapTappedForCell(_ cell: SwapCell) {
// the same logic for swapping
}
Solution from OP I adapted the code here How to access the content of a custom cell in swift using button tag?
Using delegates and protocols is the most sustainable way to achieve this I think.
I hope this helps others with the same problem!
I am writing an iOS app in Swift that puts data from an API into a TableView. The goal is that the user can scroll down a continuous list of blog posts. What is happening is that data is filling the first screen's amount of cells fine, but when the user scrolls down, the app crashes as one of the cell properties, cellImageView becomes nil and is unwrapped (as force unwrap is specified in the cell class's code):
class BlogPostEntryCell: UITableViewCell {
//MARK: Properites
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var bodyTextView: UITextView!
#IBOutlet weak var initialsImageView: UIImageView!
}
Here is the code that causes the crash:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NewCell", for: indexPath) as! NewCell
apiCaller.getAPIDataThroughAsyncCall(id: nextCell, entry: { cellContent in
DispatchQueue.main.async(execute: {
cell.cellLabel.text = cellContent.labelText
cell.cellTextView.text = cellContent.textViewText
// App crashes at the below line
cell.initialsImageView = self.createPicture(initials: cellContent.pictureText)
})
})
nextCell += 1
return cell
}
The interesting thing is that if do this:
cell.cellLabel?.text = cellContent.labelText
cell.cellTextView?.text = cellContent.textViewText
// App does NOT crash at the below line
cell.initialsImageView? = self.createPicture(initials: cellContent.pictureText)
, the app doesn't crash but the data just repeats over and over again as the user scrolls.
I've tried the solution to a similar problem here: Why does data returned from coredata become nil after tableview scroll in swift?, but that did not work. However, I do believe this has to do with the asynchronous API calls.
I am thinking that possible solutions may include population an array of API data before generating the cells, but I would like some insight on the source of this problem before a rethink the architecture of the code.
You need to make an API call in your viewDidLoad to know how many element are there and record their ID in an array. Then in your cellForRow function, you can get the id in this way
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NewCell", for: indexPath) as! NewCell
apiCaller.getAPIDataThroughAsyncCall(id: array[indexPath.row], entry: { cellContent in
DispatchQueue.main.async(execute: {
...
})
})
return cell
}
The reason your nextCell wont work is that, this cellForRow function is called every time when your table view is trying to display a cell that is not currently on your screen. Say you know you have 10 element and your screen can only display 5 of them. When you scroll to bottom, your nextCell will become 10 and everything works fine as now. But now when you scroll up, the table view have to prepare the previous 5 again. So now your nextCell becomes 15, which you don't have corresponding element in your API call.
It is bad practice to call any API in cellForRowAt indexPath: IndexPath, it produces crashes as you see, have a look at MVVM. It is like wining a war without having to go to battle and I refer to this instance where you call API in cellForRowAt indexPath: IndexPath and the outlets are not initialised.
I have the following problem:
I am making a Pokédex-like application that displays a list of all 721 Pokémon on the first tab, and another list on the second tab containing My Favorite Pokémon. Essentially, there are two identical ViewControllers connected to my TabBar.
My storyboard is as follows:
So here is the problem:
The TableView on the first (and initial) tab works fine. However, when I load the TableView on the second tab the Pokémon are loaded, but not displayed. I am able to click the TableViewCell and go to the detail page, but the label in the TableViewCell is not showing anything.
This is the code I use for loading Favorites TableView
class FavoritesViewController: BaseViewController,
UITableViewDataSource, UITableViewDelegate {
#IBOutlet var FavoritesListView: UITableView!
var pokemonList: [String] = ["Nothing Here!"]
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("FavoriteCell", forIndexPath: indexPath) as! FavoriteCell
var name = pokemonList[indexPath.row]
capitalizeFirstLetter(&name)
cell.nameLabel.text = name
return cell;
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pokemonList.count
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print(pokemonList[indexPath.row])
self.performSegueWithIdentifier("ToPokemonDetail", sender: pokemonList[indexPath.row])
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "ToPokemonDetail"){
let destination = segue.destinationViewController as! PokemonDetailViewController
let thisPokemon = sender as! String
destination.currentPokemon = thisPokemon
}
}
override func viewWillAppear(animated: Bool) {
FavoritesListView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// Fetch the cached list, getNames returns an array of strings
let list = utility.getNames("Favorites")
pokemonList = list
}
The delegate and the dataSource are set via the storyboard.
The above code works, and shows the Favorites list just fine. The class for the complete Pokédex has a similar construction.
I have tried switching Favorites and Pokédex around, so that it shows the complete Pokémon list on startup. All 721 Pokémon are shown correctly, but then the Favorites are not visible.
What else I have tried:
Checking the Reuse Identifiers, over and over
Referencing outlets should be bound correctly
Calling TableView.reloadData() in the viewDidAppear method
Switching around the tab items
Does anyone have any clue what on earth is going on here?
Feel free to ask any more questions
Edit: this is what happens when I swap the two TabBar Buttons around, no code changes
Pokédex Screen
Favorites Screen
GitHub Project Here
Problem is in storyboard cell label frame. Set constraints of view controller for (Any,Any) Size Class. I can commit the code on github if you can give me write rights on your git. Thanks
Perhaps your table's delegate and dataSource are not set.
table.delegate = self
table.dataSource = self
Of course this is after you add the properties to your view controller
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
Your number of rows is always 0 for that controller,
I looked into your code pokemonList count is always 0 its not updating data in it
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pokemonList.count
}
The big issue is your PokemonDetailViewController is not a UITableViewController. It needs to inherent from UITableViewDataSource, UITableViewDelegate and then be connected to the storyboard view to provide data and formatting for a table.
I have been learning swift through the last few days and I have come across an error that I have been stuck on for quite a while now.
I am attempting to get the selected indexPath so that I can then push data according to which item he selected. I have searched through and tried many different solutions I have found on stack overflow as well as different websites but I am not able to get this figured out still.
The code is below:
#IBOutlet var selectGroceryTable: UITableView!
/* Get size of table */
func tableView(tableView: UITableView, numberOfRowsInSection: Int) ->Int
{
return grocery.count;
}
/* Fill the rows with data */
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let myCell:UITableViewCell = selectGroceryTable.dequeueReusableCellWithIdentifier("groceryListRow", forIndexPath:indexPath) as! UITableViewCell
myCell.textLabel?.text = grocery[indexPath.row];
myCell.imageView?.image = UIImage(named: groceryImage[indexPath.row]);
return myCell;
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
print("Row Selected");
NSLog("Row Selected");
}
Nothing ever prints acting like the function is not being called. However, I do not understand why this would not be called?
Any help would be greatly appreciated!
UPDATE:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
selectGroceryTable.data = self;
selectGroceryTable.delegate = self; //gives error states you can not do this
}
There are a couple of things to check in cases like this:
First, what kind of method is didSelectRowAtIndexPath?
Answer: It's a UITableViewDelegate method. Did you set your view controller up as the delegate of the table view? If not, this method won't get called.
Second, have you made absolutely certain that the method signature is a perfect match for the method from the protocol? A single letter out of place, the wrong upper/lower case, a wrong parameter, and it is a different method, and won't be called. it pays to copy the method signature right out of the protocol header file and then fill in the body to avoid minor typos with delegate methods.
It looks to me like your method signature is correct, so my money is on forgetting to set your view controller up as the table view's delegate.
First of all, I've looked around to find a solution to my problem, here and on other websites. If I've missed something please show me the link, i didn't intend on bugging you with my problem if there is a solution somewhere else.
My idea was to create an app (just for myself as a practise since I'm fairly new to swift) that would get the NBA schedule from a website, extract the games and results and show them in a table. For that I made a textField where the user could enter from which game day he wanted the results. The Integer he enters changes the url and the url is propperly spilt up and the data I want to display is saved in an array as a string.
Thats were my problem occurs. The items are appended to the array and the array.count displays the right number depending on the day the user entered. The only problem is that the data from the array is not display in the table cell. I've rewrote the code and made sure I didn't mess up the table, but as soon as I add the second part of the app (the information that got from the URL) to the app, the cells don't display anything.
It's kind of weird because both parts are working fine on their own, but as soon as I combine them my problem occurs. Do you know where I might have messed up?
Does anyone have an idea what my mistake may be? I'm not looking for code solutions, just for someone who might tell me where the flaw in my logic is. Maybe i missed something, but i don't get why my cells are not displaying the elements of the array, even though the array is set up properly.
Thanks in advance to anyone answering and have a nice day!
Greetings!
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var gamesArray = [String]()
var gameDay:Int = 0
#IBOutlet var textField: UITextField!
#IBOutlet var gamesTable: UITableView!
#IBAction func enterButton(sender: AnyObject) {
gameDay = Int(textField.text!)!
// webCodeArrayForGames is where i temporarily put the strings I want to add
for var counter = 1; counter<webCodeArrayForGames.count; counter++{
self.gamesArray.append(webCodeArrayForGames[counter])
}
override func viewDidLoad()
{
super.viewDidLoad()
gamesTable.delegate = self
gamesTable.dataSource = self
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return gamesArray.count
}
func TableView(tableView: UITableView, cellforRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "cell")
cell.textLabel?.text = gamesArray[indexPath.row]
return cell
}
override func viewDidAppear(animated: Bool) {
self.gamesTable.reloadData()
}
}
You can check out this one:
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/TableView_iPhone/TableViewAPIOverview/TableViewAPIOverview.html
On the short, you have to provide UITableViewDelegate and UITableViewDataSource.
From the code, I suspect that you didn't provide the dataSource for your table:
"The data source adopts the UITableViewDataSource protocol. UITableViewDataSource has two required methods. The tableView:numberOfRowsInSection: method tells the table view how many rows to display in each section, and the tableView:cellForRowAtIndexPath: method provides the cell to display for each row in the table. "
You could do this way:
Subclass your viewController from UITableViewDelegate and UITableViewDataSource
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { ....
in viewDidLoad() assign the tableview's delegate and dataSource:
override func viewDidLoad() {
super.viewDidLoad()
gamesTable.delegate = self
gamesTable.dataSource = self ....
}