I have an iOS project I'm working on using Xcode7 and Swift. The user can add items to an Array called "details" that is viewed in a TableView. Is there a way to specify the location of where you want the new item to be? Let's say I have 5 items in my array and I want the next added item to be placed in location indexPath.row for position 3. How do I do that? How can I make the new details[indexPath.row] be 3 for that item?
You should add the item into the "details" array at a specific index, then reload the table view.
details.insert("This String", atIndex: 3)
self.tableView.reloadData()
Let's say details is:
var details = ["Hello", "Hi", "Bye", "Goodbye", "No"]
Wherever you want to add the next item (I'm assuming position 3 means the 3rd index):
details.insert("Hey", atIndex: 3)
Then reload the table view:
tableView.reloadData()
Just insert the item in details at the requested index and call tableView.reloadData()or tableView.insertRowsAtIndexPaths:withRowAnimation: passing the same index wrapped in [NSIndexPath]. But first check if the index is not out of bounds.
Related
I have tableview where I need to show few sections. You should imagine this table like a playlist of your songs. In the top first section I need to display a cell with button which will add more songs to the playlist and other sections of tableview are header titles of Music category (like pop, rock and etc). Each of these sections contains cells which are songs names.
I have an array of songs called like this: var songsGroups = [SongGroup]. Which is actually my datasource.
SongGroup contains few properties:
var categoryName: String
var songs: [Songs]
But the problem appears on the next level. I every time need to check indexPath.section and do like this:
if indexPath.section == 0 {
// this is a section for ADD NEW SONG BUTTON cell no need in header title as there is no data repression only static text on the cell.
} else {
var musicCategoryName = songsGroups[indexPath.seciton - 1]. categoryName
headerTitle.title = musicCategoryName
}
As you see my code became magical by adding this cool -1 magical number. Which I replay don't love at all.
As an idea for sure I can try to combine my ADD NEW SONG BUTTON section (by adding some additional object) with songsGroups array and create NSArray for this purposes. Like in Objective-C as you remember. So then my datasource array will looks like this:
some NSArray = ["empty data for first cell", songsGroups[0], songsGroups[1]... etc]
So then there is no need to check any sections we can trust our array to build everything and even if I will add more empty data cells there is no need for me to handle my code via if block and adding tons of magical numbers.
But the issue I see here that we don't use explicit types of array and it's upset.
So maybe you know more beautiful solutions how to resolve my issue.
You can introduce a helper enum:
enum Section {
case empty
case songCategory(categoryName: String, songs: [String])
}
Your data source would then look something like this:
let datasource: [Section] = [.empty, .songCategory(categoryName: "Category1", songs: ["Song 1", "Song2"])]
So now you can use pattern matching to fill the table view:
let section = datasource[indexPath.section]
if case let .songCategory(categoryName, songs) = section {
headerTitle.title = categoryName
} else {
// this is a section for ADD NEW SONG BUTTON cell no need in header title as there is no data repression only static text on the cell.
}
I am not sure if I understand you right. But is seems to me that you want to display
1) something that lets the user add a new song by tapping a button, and
2) a table of songs, sectioned into groups.
If this is the case, why don’t you put the add new song button in the table header view, and all your song groups and songs in a 2-dim array used as your dataSource?
I have main UITableView with some Todos that are populated used an array of Observables:
// on viewDidLoad
self.todosViewModel.todos.asObservable()
.bind(to: tableView.rx.items(cellIdentifier: "TodoViewCell", cellType: TodoTableViewCell.self)) { (row, todo, cell) in
cell.status.image = todo.getStatusImage()
cell.title.text = todo.title.value
cell.todoDescription.text = todo.description.value
cell.dueDate.text = String(describing: Utilities.dateFormatter.string(from: todo.dueDate.value))
}.disposed(by: disposeBag)
On another screen I can add/edit the data and then, click "save" button to append a new todo or modify the one being edited. This works great, except that the tableView don't reload automatically, forcing me to call tableView.reloadData() on viewDidAppear, which is triggered when the other screen is dismissed.
So,
Is there a native way for me to reload a table automatically when then todosViewModel variable is updated?
EDIT:
If on the screen of edition of the todo I reassociate the todosViewModel's value property with the same value, it also works:
self.todosViewModel.todos.value = self.todosViewModel.todos.value
That's pretty ugly, but I only know how to reload a tableView using one of those ways.
When I append a new todo or reassociate a new value to the base todos it works. When I edit, no.
That's the whole point. For the UITableView to update, your todos have to emit when an item inside has been edited or you have to bind something from your todo within the cell (this way you cannot change hight of the cell but you can push some dynamic information onto the cell).
Another option would be to map some observable from todo to the index of the cell where it's presented and call UITableView.reloadRows to update your edited cell.
I would recommend you to take a look at RxDataSources examples.
I have a table view with multiple selection enabled. Users are supposed to be able to select multiple options in a list and then change the sort order of the list. I would like to be able to keep the same cells selected, but the order of them will have changed from the sort. I'm not seeing a way to achieve this so any suggestions are appreciated.
If you have a "selected" indicator in your data structure, you could use it to rebuild the tableView selection.
If you can't add such an indicator to the data, you could perform your sort in two steps: 1) compute the new element indexes, 2) apply these indexes to your data array to perform the sort.
With the sort indexes, you can rebuild the selection based on the new location of the original indexes:
For example:
// Assuming your tableview is named "tableView",
// and the data array is named "dataArray",
// and you sort uses a member of dataArray called "sortKey"
// prepare list of new indexes for sorted data
let newIndexes = dataArray.enumerated().sorted{ $0.1.sortKey < $1.1.sortKey }.map{$0.0}
// determine new indexes of current selection
let newSelection = tableView.indexPathsForSelectedRows?
.map{IndexPath(row:newIndexes.index(of:$0.row)!, section:0)}
// sort and reload the data
dataArray = newIndexes.map{ self.dataArray[$0] }
tableView.reloadData()
// reapply the selection
tableView.indexPathsForSelectedRows?.forEach{tableView.deselectRow(at:$0, animated:false)}
for indexPath in newSelection ?? []
{ self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none) }
I am new in Swift. The sample codes about UITableView show that new item is placed on the bottom of the list. How may we reverse this? I searched internet but could not find an answer.
Thanks.
Guven
UITableviews show data based on the order of an array such as an array of characters like
var data = ["B","C","D","E"]
Typically, you add data into array by using append which adds data at the end of the array, hence why it adds it at the bottom of the list.
data.append("A")
If you want your table view to add data on top of the list, then you can add your data at the beginning of the array like this.
data.insert("A", at: 0)
now reload your tableView, and new data would be added at the top of the list
yourTableViewName.reloadData()
To put new item on top, insert it at desired position (index 0) and reload corresponding indexPath (row: 0, section: 0).
let indexPathOfFirstRow = NSIndexPath.init(forRow: 0, inSection: 0)
yourArray.insert("some element", atIndex: 0)
tableView.beginUpdates()
tableView.insertRowsAtIndexPaths([indexPathOfFirstRow], withRowAnimation: .Automatic)
tableView.endUpdates()
Reloading whole table is a costly task and not recommended.
Add item to the array last index .
Then reload tableView
All you really need to do is to .insert your object at index 0 rather than append. Appending your latest value at the first index 0 ensures your last data is shown at the top. You can continue using tableview.reloaddata()as usual. Hope the screenshot helps
Inserting new items on data array at 0 index is easy but keeping the state of tableview as it is difficult especially when you are dealing with headers too...
I am using this to append old messages when paginating the chat...
you can iterate on data and insert new items and header with data.insert("new message", at: 0) but tableview automatically going to jump on zero index. to keep that state you should need a unique id in every new item in my case it was time and messageId
I am saving the last message id before appending new items ... and using that id to calculate the indexPath of the previous top message cell and scroll to it with no animation...
func getIndexPath(WithMessageId id: Double) -> IndexPath? {
for (section, sectionObjects) in messagesArray.enumerated() {
if let index = sectionObjects.Messages?.firstIndex(where: { $0.MessageID == id }) {
return IndexPath(row: index, section: section)
}
}
return nil
}
this method returns the indexpath use that as follows
self.tableView.reloadData()
self.tableView.scrollToRow(at: getIndexPath(WithMessageId: lastMessageID) ?? IndexPath(row: 0, section: 0),
at: .top, animated: false)
its gonna append new items without even any glitches like Twitter appending new news feeds on top...
So the number of buttons on screen change with number of items in array from API.
I have a simple example, i have an array of integers and for each item in the array i am going to create a UIButton and add it to array of UIButton.
let array = [1,2,3,4]
var buttons: [UIButton] = []
for item in array {
let button = UIButton()
button.titleLabel?.text = "Button \(item)"
buttons.append(button)
}
print("Number of buttons: \(buttons.count)")
Based on your information you should not create a button for each item in the array you collect from an external API. Since you are using a collection view I would design a collection view cell with the appropriate properties. One of the properties should be a unique identifier. This standard collection view cell is the model for all your collection view cells, that represent your buttons.
Then use the collection view delegate method: collectionView(_:didSelectItemAt:) to react on a tap/press from the user. In this method the functionality that you use will be based on the unique identifier of your collection view cell.
There is no need to create a button in a collection view. The collection view already has all the functionality in its collection view item.