UICollectionView reloads data without calling reloadData() - ios

I have structured my app using the MVVM pattern, the datasource of the collectionView gets data from the viewModel.
In viewModel I have a closure which gets called after updating the data, it passes IndexSet of sections and [IndexPath] of items which collectionView should insert. However I get the crash everytime after calling the insert method with error:
'Invalid update: invalid number of sections. The number of sections contained in the collection view after the update (11) must be equal to the number of sections contained in the collection view before the update (11), plus or minus the number of sections inserted or deleted (11 inserted, 0 deleted).'
I understand what this error means, however I noticed that this is the order in which the methods are called
1. viewDidLoad()
2. numberOfSections(in collectionView: UICollectionView) // returns 0
3. update?(insertedSectionsSet, insertedRows) // the closure gets called in viewModel, insertedSectionsSet has 11 elements, that's correct
4. numberOfSections(in collectionView: UICollectionView) // returns 11, that's correct but it should be called after next point
5. viewModel.update = { [weak self] sections, items in
DispatchQueue.main.async {
self?.collectionView.performBatchUpdates({
self?.collectionView.insertSections(sections) // crash
self?.collectionView.insertItems(at: items)
}, completion: nil)
}
}
As you can see in the code below, I added prints before calling each methods and you can clearly see that numberOfSections gets called after calling the closure and before performing it. It makes absolutely no sense to me why is it happening. I believe that the cause of crash lies in calling the numberOfSections before inserting them, because then it expects 22 sections after inserting.
update
numberofsections
closure
numberofsections
Code:
class PlaylistsDataSource: NSObject, UICollectionViewDataSource {
...
func numberOfSections(in collectionView: UICollectionView) -> Int {
print("numberofsections")
return viewModel.numberOfSections
}
...
}
class PlaylistsMasterViewController: UIViewController {
...
viewDidLoad() {
viewModel.update = { [weak self] sections, items in
DispatchQueue.main.async {
print("closure")
self?.collectionView.performBatchUpdates({
self?.collectionView.insertSections(sections)
self?.collectionView.insertItems(at: items)
}, completion: nil)
}
}
}
...
}
class PlaylistsMasterViewModel {
private var sectionIndexes = [String]()
private var playlistsDictionary = [String: [AWPlaylist]]()
var update: ((IndexSet, [IndexPath]) -> Void)?
var numberOfSections: Int {
return sectionIndexes.count
}
EDIT: Added more code
extension PlaylistsMasterViewModel {
func fetchPlaylists() {
repo.getAllPlaylists(from: [.iTunes]) { [weak self] result in
switch result {
case .success(let playlists):
self?.sortIncoming(playlists: playlists)
case .failure(let error):
print(error.localizedDescription)
}
}
}
private func sortIncoming(playlists: [AWPlaylist]) {
var insertedPlaylists = [(key: String, list: AWPlaylist)]()
var insertedIndexes = [String]()
func insertNewSection(playlist: AWPlaylist, key: String) {
insertedIndexes.append(key)
playlistsDictionary.updateValue([playlist], forKey: key)
}
func insertNewRow(playlist: AWPlaylist, key: String) {
guard var value = playlistsDictionary[key] else {
print("Oh shit")
return
}
value.append(playlist)
value.sort(by: SortingPredicates.Playlist.nameAscending)
playlistsDictionary.updateValue(value, forKey: key)
insertedPlaylists.append((key, playlist))
}
for list in playlists {
let name = list.localizedName.uppercased().trimmingCharacters(in: CharacterSet.whitespaces)
guard let firstCharacter = name.first else { return }
let firstLetter = String(firstCharacter)
let key: String
if CharacterSet.english.contains(firstLetter.unicodeScalars.first!) {
key = firstLetter
} else if CharacterSet.numbers.contains(firstLetter.unicodeScalars.first!) {
key = "#"
} else {
key = "?"
}
if playlistsDictionary[key] == nil {
insertNewSection(playlist: list, key: key)
} else {
insertNewRow(playlist: list, key: key)
}
}
sectionIndexes.append(contentsOf: insertedIndexes)
sectionIndexes.sort(by: { $0 < $1 })
let insertedSections = insertedIndexes.compactMap { index -> Int? in
guard let sectionIndex = self.sectionIndexes.firstIndex(of: index) else {
return nil
}
return sectionIndex
}
let insertedSectionsSet = IndexSet(insertedSections)
let insertedRows = insertedPlaylists.compactMap { tuple -> IndexPath? in
if let section = self.sectionIndexes.firstIndex(of: tuple.key) {
if let row = self.playlistsDictionary[tuple.key]?.firstIndex(where: { $0.equals(tuple.list) }) {
return IndexPath(row: row, section: section)
}
}
return nil
}
print("update")
update?(insertedSectionsSet, insertedRows)
}

Related

ReactiveSwift Refresh data

I beginner in ReactiveSwift. This is fetching code in my view model :
private let viewDidLoadProperty = MutableProperty<Void?>(nil)
public func viewDidLoad() {
disposables += self.weatherFetcher.fetchCurrentWeather().startWithResult { (result) in
switch result {
case .success(let value):
_ = value?.list?
.map { [weak self] weatherData in
if let weather = weatherData.weather?.first {
self?.weatherFetcher.fetchWeatherImage(icon: weather.icon).startWithResult { (result) in
switch result {
case .success(let iconData):
self?.cellViewModels.append(WeatherCellViewModel(with: weatherData, iconData: iconData))
case .failure(let error):
print("something went wrong - \(error)")
}
}
} else {
self?.cellViewModels.append(WeatherCellViewModel(with: weatherData, iconData: nil))
}
}
case .failure(let error):
print(error)
}
}
self.viewDidLoadProperty.value = ()
}
When viewDidLoad is called in ViewController then view model starts fetching data. How to tell VC that fetch is end and refreshData can be called? Is any possibility to catch end of viewDidLoad func, I mean after fetching.
initCode :
init(weatherFetcher: WeatherFetcher) {
self.weatherFetcher = weatherFetcher
didStartLoadingWeather = self.viewDidLoadProperty.signal.skipNil()
}
I would first of all advise you on using a ViewModel, that would be in charge of doing these operations in behalf of the UIViewController.
Answering your question directly. You will have to use some sort of mechanism to hold to the data. This can be either a Property or MutableProperty. My advice is for the former. You will also need a trigger, so when viewDidLoad happens, you can communicate this. Assuming you have a ViewModel:
import ReactiveSwift
import enum Result.NoError
public enum ViewState<T> {
case loading
case loaded([T])
case failure(YourError)
}
class ViewModel {
private let (signal, observer) = Signal<Void, NoError>.pipe()
let state: Property<ViewState<WeatherCellViewModel>>
init() {
let fetch = signal.flatMap(.latest, transform: fetcher)
self.state = Property.init(initial: .loading then: fetch)
}
func fetch() {
observer.send(value: ())
}
}
I will leave the completion of the fetch for you. But:
This approach allows you to keep state around (via a property) and allow for an external trigger.
You would now read the values from the state.
the fetcher is created at initialization time and only triggered after fetch function is called.
A good practice for loading table views using ReactiveSwift (if that's your case) is that just simply bind your table view data to a MutableProperty<[your data]>
you just simply fetch your data and when the value is received, reload table view. After that, table view will be magically refreshed.
in your view model:
struct WeatherInfo {
var temp: Int
var icon: String
}
var data: MutableProperty<[WeatherInfo]>([])
func fetch() -> SignalProducer<Bool, SomeError> {
return weatherFetcher.fetchCurrentWeather().on(value: { val in
let mapped = myCustomMapFunc(val) //transforms 'val' to [WeatherInfo]
self.data.swap(mapped)
})
}
in your view controller:
let viewModel = MyViewModel()
func viewDidLoad() {
viewModel.fetch().startWithCompleted {
self.tableView.reloadData()
}
}
public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.data.value.count
}
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "myCellId", for:indexPath) as! MyCustomCell
let info = viewModel.data.value[indexPath.row]
cell.txtTemp.text = info.temp
cell.icon = info.icon
return cell
}
If it's not the table view, just keep the idea and do it on whatever you want. For example, if it's a simple cell, load the cell with new data:
in your view model:
// var data: MutableProperty<WeatherInfo>([]) //you don't need it anymore
func fetch() -> SignalProducer<WethearInfo, SomeError> {
return weatherFetcher.fetchCurrentWeather().map({
return myCustomMapFunc($0) //transforms the input into WeatherInfo
})
}
in your view controller:
let viewModel = MyViewModel()
func viewDidLoad() {
viewModel.fetch().startWithValues { val in
self.reloadMyCell(info: val)
}
}
func reloadMyCell(info: WeatherInfo) {
mycell.temp = info.temp
mycell.icon = info.icon
}

sorting cells in table view through buttons

I have a table view with data from API.
Each cell has several properties. What I want, is to sorting cells by chosen button (these three rectangles).
I dont know exactly how should I do it. I think I need tree methods for that, each for clicked button. But how to deal with sorting and reloading data?
Thanks for answer.
override func viewDidLoad() {
super.viewDidLoad()
callAlamo(url: urlAPI)
}
func callAlamo(url: String){
Alamofire.request(url).responseJSON(completionHandler: {
response in
self.parseData(JSONData: response.data!)
})
}
func parseData(JSONData: Data){
do{
var readableJSON = try JSONSerialization.jsonObject(with: JSONData, options: .mutableContainers) as! JSONStandard
// print(readableJSON)
if let rows = readableJSON["rows"] as? [JSONStandard]{
print(rows)
for i in 0..<rows.count{
let row = rows[i]
if let name = row["name"] as? String{
if name.isEmpty == false{
if let status = row["status"] as? String{
if let counter = row["counter"] as? String{
items.append(Station.init(place: name, counter: counter, status: status))
DispatchQueue.main.async {
self.tableView.reloadData()
}
}else{
let counter = "no data"
items.append(Station.init(place: name, stationsCount: counter, status: status))
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
}
}
}catch{
print(error)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
cell.place.text = items[indexPath.row].place
cell.stationsCount.text = items[indexPath.row].stationsCount
let num = items[indexPath.row].status
let value = picDictionary[num!]
print(num!, value!)
cell.statusSign.image = UIImage(named: value!)
return cell
}
Sometimes I get null value from API, and then, I assing value to string "no data".
if let counter = row["counter"] as? String{
/////code
else{
let counter = "no data"
//////
}
And I dont want to let these values taking part in sorting process, because they are not numbers. How to do that?
Make three actions for your three buttons and sort the array with it and reload the table after that.
#IBAction func sortWithName(_ sender: UIButton) {
items.sort { $0.place < $1.place }
self.tableView.reloadData()
}
#IBAction func sortWithStatus(_ sender: UIButton) {
items.sort { $0.status < $1.status }
self.tableView.reloadData()
}
#IBAction func sortWithStatusCount(_ sender: UIButton) {
//stationsCount is looks like number so sort it using compare with numeric option
items.sort { $0.stationsCount.compare($1.stationsCount, options: .numeric) == .orderedAscending }
self.tableView.reloadData()
}
Edit: Declare one more array named allItems same way you have declared your items array. One more thing I haven't noticed first that you are reloading tableView inside for loop so that it will reload n(o) times instead of that you need to reload it once after the for loop also before reloading the tableView and set allItems with your items also you are making too many if condition where you can combine it in once.
for row in rows {
if let name = row["name"] as? String,
let status = row["status"] as? String,
!name.isEmpty {
if let counter = row["counter"] as? String {
items.append(Station.init(place: name, counter: counter, status: status))
}else{
let counter = "no data"
items.append(Station.init(place: name, stationsCount: counter, status: status))
}
}
}
//Set allItems with items
self.allItems = items
DispatchQueue.main.async {
self.tableView.reloadData()
}
Now change all your button actions like this.
ke three actions for your three buttons and sort the array with it and reload the table after that.
#IBAction func sortWithName(_ sender: UIButton) {
items = allItems.sorted { $0.place < $1.place }
self.tableView.reloadData()
}
#IBAction func sortWithStatus(_ sender: UIButton) {
items = allItems.sorted { $0.status < $1.status }
self.tableView.reloadData()
}
#IBAction func sortWithStatusCount(_ sender: UIButton) {
//Filter your array first
items = allItems.filter { $0.stationsCount != "no data" }
//stationsCount is looks like number so sort it using compare with numeric option
items.sort { $0.stationsCount.compare($1.stationsCount, options: .numeric) == .orderedAscending }
self.tableView.reloadData()
}

Deleting of rows in UITableView takes too much time

I'm writing a program which have array of 40k (40000) items, and present it in UITableView, after search table should filter and present only search results.
The problem is that deleting of many rows at once (for example 30000+) takes about 10 - 20 seconds and sure it's not possible to use. Can you suggest any decision of this problem?
(tableview.reloadData() not suited)
var allProducts = [Product]()
#IBOutlet weak var searchTableView: UITableView!
#IBOutlet weak var searchTextField: UITextField?
var searchResults = [Product]()
enum Action{
case Insert
case Ignore
case Remove
}
override func viewDidLoad() {
super.viewDidLoad()
searchTextField?.addTarget(self, action: #selector(ViewController.textFieldDidChanged(_:)), for: .editingChanged)
DBBrain().getAllAlcProducts() { [weak self] products in
self?.allProducts = products
}
}
func textFieldDidChanged(_ sender: UITextField){
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
let text = sender.text!.lowercased()
let res = self!.allProducts.filter({ $0.name.lowercased().contains(text) })
if self?.searchTextField?.text != nil && text == self!.searchTextField!.text!{
if let values = self?.getIndexes(forResults: res){
self?.searchResults = res
self?.updateTable(action: values.0, indexes: values.1)
}
}
}
}
private func getIndexes(forResults products: [Product]) -> (Action, [IndexPath]){
var indexes = [IndexPath]()
var action = Action.Ignore
if searchResults.count > products.count{
var newCounter = 0
for x in 0..<searchResults.count {
if products.isEmpty || searchResults[x].id != products[newCounter].id {
indexes.append(IndexPath(row: x, section: 0))
}else {
if newCounter < products.count - 1{
newCounter += 1
}
}
}
action = Action.Remove
}else if searchResults.count < products.count{
var oldCounter = 0
for x in 0..<products.count {
if searchResults.isEmpty || searchResults[oldCounter].id != products[x].id {
indexes.append(IndexPath(row: x, section: 0))
}else {
if oldCounter < searchResults.count - 1 {
oldCounter += 1
}
}
}
action = Action.Insert
}
return (action, indexes)
}
private func updateTable(action: Action, indexes: [IndexPath]) {
DispatchQueue.main.async { [weak self] in
if action != .Ignore {
if action == .Remove {
self?.searchTableView.beginUpdates()
self?.searchTableView.deleteRows(at: indexes, with: .fade)
self?.searchTableView.endUpdates()
}else if action == .Insert {
self?.searchTableView.beginUpdates()
self?.searchTableView.insertRows(at: indexes, with: .fade)
self?.searchTableView.endUpdates()
}
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return searchResults.count
}
You should never have a table with 30k+ cells. That's a ridiculous usage of memory. You should set a max limit to the number of cells in your rowsForSection delegate method.
Get the count of your results, and if the count is greater than 30 or so (even that is a large number), set it to 30.
It may very well be having a super huge array, too. That's a lot of memory usage, even if you aren't making cells for those.

Swift Compiler Warning : Result of call to 'save(defaults:)' is unused

So my table view is not loading anything and I think it's because of this warning that I get. It saids the save function is not being used so how can it load something that is not saved. What I am saving is the indexPath and Section of the row that the user selected via a button action in the row.
Warning:
Result of call to 'save(defaults:)' is unused
Code:
func saveSorting(_ dataIdBlock: (Any) -> String) {
guard let items = self.items else { return }
for (section, rows) in items.enumerated() {
for (row, item) in rows.enumerated() {
let indexPath = IndexPath(row: row, section: section)
let dataId = dataIdBlock(item)
let ordering = DataHandling(dataId: dataId, indexPath: indexPath)
// Warning is here
ordering.save(defaults: indexPath.defaultsKey)
}
}
}
}
NSCoder Class for DataHandling / ordering.save
DataHandling.swift
class DataHandling: NSObject, NSCoding {
var indexPath: IndexPath?
var dataId: String?
init(dataId: String, indexPath: IndexPath) {
super.init()
self.dataId = dataId
self.indexPath = indexPath
}
required init(coder aDecoder: NSCoder) {
if let dataId = aDecoder.decodeObject(forKey: "dataId") as? String {
self.dataId = dataId
}
if let indexPath = aDecoder.decodeObject(forKey: "indexPath") as? IndexPath {
self.indexPath = indexPath
}
}
func encode(with aCoder: NSCoder) {
aCoder.encode(dataId, forKey: "dataId")
aCoder.encode(indexPath, forKey: "indexPath")
}
func save(defaults box: String) -> Bool {
let defaults = UserDefaults.standard
let savedData = NSKeyedArchiver.archivedData(withRootObject: self)
defaults.set(savedData, forKey: box)
return defaults.synchronize()
}
convenience init?(defaults box: String) {
let defaults = UserDefaults.standard
if let data = defaults.object(forKey: box) as? Data,
let obj = NSKeyedUnarchiver.unarchiveObject(with: data) as? DataHandling,
let dataId = obj.dataId,
let indexPath = obj.indexPath {
self.init(dataId: dataId, indexPath: indexPath)
} else {
return nil
}
}
class func allSavedOrdering(_ maxRows: Int) -> [Int: [DataHandling]] {
var result: [Int: [DataHandling]] = [:]
for section in 0...1 {
var rows: [DataHandling] = []
for row in 0..<maxRows {
let indexPath = IndexPath(row: row, section: section)
if let ordering = DataHandling(defaults: indexPath.defaultsKey) {
rows.append(ordering)
}
rows.sort(by: { $0.indexPath! < $1.indexPath! })
}
result[section] = rows
}
return result
}
}
Other code I'm using:
// Number of Rows in Section
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items?[section].count ?? 0
}
// Number of Sections
func numberOfSections(in tableView: UITableView) -> Int {
return self.items?.count ?? 0
}
Saving it with:
saveSorting() { "\($0)" }
Loading it in ViewDidLoad:
func fetchData() {
// Load Data from Server to testArray
retrieveData()
// request from remote or local
data = [testArray]
// Update the items to first section has 0 elements,
// and place all data in section 1
items = [[], data ?? []]
// apply ordering
applySorting() { "\($0)" }
// save ordering
saveSorting() { "\($0)" }
// refresh the table view
myTableView.reloadData()
}
Loading Code:
// Loading
func applySorting(_ dataIdBlock: (Any) -> String) {
// get all saved ordering
guard let data = self.data else { return }
let ordering = DataHandling.allSavedOrdering(data.count)
var result: [[Any]] = [[], []]
for (section, ordering) in ordering {
guard section <= 1 else { continue } // make sure the section is 0 or 1
let rows = data.filter({ obj -> Bool in
return ordering.index(where: { $0.dataId == .some(dataIdBlock(obj)) }) != nil
})
result[section] = rows
}
self.items = result
}
The DataHandling instance's save(defaults:) function technically returns a value, even if you don't use it. To silence this warning, assign it to _ to signify that you don't intend to use the result value, e.g.:
_ = ordering.save(defaults: indexPath.defaultsKey)
or
let _ = ordering.save(defaults: indexPath.defaultsKey)
Just to be clear, this is almost definitely not why your tableview is not loading data. It should be pretty insignificant. The indexPath.defaultsKey is being saved (assuming the API works).

Guidance on Core Data with Swift 3

Not looking for code (wouldn't hurt though) just looking to know how I should go about this. I have an app which is like a blog reader. I have information in a MYSQL database which is taken with JSON, placed in a jsonArray and then placed in an array to be shown in a table view. In that table view I generally have all my objects in 1 section. Each row/object has a button when clicked it moves that row into another section. I also have a search controller to search through the main section (Section 1).
How do I save the order or position of the rows to Core Data?
For example: I have 0 rows in Section 0 and 5 rows in Section 1, I click the button on one of the rows in Section 1 and it moves that row to Section 0, now Section 0 has 1 row while Section 1 has 4 rows. How do I save this new tableview order to Core Data? I just want to save the rows positions, so the app remembers which section that selected row was in.
Do I save the indexPath of the row in the section?
When adding an entity and attributes, what do I use to save?
Also, its a mysql reader so when the tableview is reloaded and a new content is added, will it still show since the tableview will be reading from core data?
I'm learning Core Data (Swift 3 code) but just having trouble using it for this app.
Thank you for helping out!
Use UserDefaults to save the ordering data if you don't need to save the whole data from MySQL database. Define a class only contains dataId and indexPath:
class DataOrdering: NSObject, NSCoding {
var indexPath: IndexPath?
var dataId: String?
init(dataId: String, indexPath: IndexPath) {
super.init()
self.dataId = dataId
self.indexPath = indexPath
}
required init(coder aDecoder: NSCoder) {
if let dataId = aDecoder.decodeObject(forKey: "dataId") as? String {
self.dataId = dataId
}
if let indexPath = aDecoder.decodeObject(forKey: "indexPath") as? IndexPath {
self.indexPath = indexPath
}
}
func encode(with aCoder: NSCoder) {
aCoder.encode(dataId, forKey: "dataId")
aCoder.encode(indexPath, forKey: "indexPath")
}
func save(defaults key: String) -> Bool {
let defaults = UserDefaults.standard
let savedData = NSKeyedArchiver.archivedData(withRootObject: self)
defaults.set(savedData, forKey: key)
return defaults.synchronize()
}
convenience init?(defaults key: String) {
let defaults = UserDefaults.standard
if let data = defaults.object(forKey: key) as? Data,
let obj = NSKeyedUnarchiver.unarchiveObject(with: data) as? DataOrdering,
let dataId = obj.dataId,
let indexPath = obj.indexPath {
self.init(dataId: dataId, indexPath: indexPath)
} else {
return nil
}
}
class func allSavedOrdering(_ maxRows: Int) -> [Int: [DataOrdering]] {
var result: [Int: [DataOrdering]] = [:]
for section in 0...1 {
var rows: [DataOrdering] = []
for row in 0..<maxRows {
let indexPath = IndexPath(row: row, section: section)
if let ordering = DataOrdering(defaults: indexPath.defaultsKey) {
rows.append(ordering)
}
rows.sort(by: { $0.indexPath! < $1.indexPath! })
}
result[section] = rows
}
return result
}
}
Playground sample:
let data = DataOrdering(dataId: "1", indexPath: IndexPath(row: 0, section: 0))
let savedData = NSKeyedArchiver.archivedData(withRootObject: data)
let obj = NSKeyedUnarchiver.unarchiveObject(with: savedData) as? DataOrdering
obj?.dataId // print: "1"
obj?.indexPath // print: [0,0]
Use save function to save with a "key" and read it back by DataOrdering(defaults: "key")
UPDATED
Added codes for a view controller to use this class:
extension IndexPath {
var defaultsKey: String { return "data_ordering_\(section)_\(row)" }
}
class ViewController: UITableViewController {
var data: [Any]?
var items: [[Any]]?
func fetchData() {
// request from remote or local
data = [1, 2, 3, "a", "b", "c"] // sample data
// Update the items to first section has 0 elements,
// and place all data in section 1
items = [[], data ?? []]
// apply ordering
applySorting() { "\($0)" }
// save ordering
saveSorting() { "\($0)" }
// refresh the table view
tableView.reloadData()
}
func applySorting(_ dataIdBlock: (Any) -> String) {
// get all saved ordering
guard let data = self.data else { return }
let ordering = DataOrdering.allSavedOrdering(data.count)
var result: [[Any]] = [[], []]
for (section, ordering) in ordering {
guard section <= 1 else { continue } // make sure the section is 0 or 1
let rows = data.filter({ obj -> Bool in
return ordering.index(where: { $0.dataId == .some(dataIdBlock(obj)) }) != nil
})
result[section] = rows
}
self.items = result
}
func saveSorting(_ dataIdBlock: (Any) -> String) {
guard let items = self.items else { return }
for (section, rows) in items.enumerated() {
for (row, item) in rows.enumerated() {
let indexPath = IndexPath(row: row, section: section)
let dataId = dataIdBlock(item)
let ordering = DataOrdering(dataId: dataId, indexPath: indexPath)
ordering.save(defaults: indexPath.defaultsKey)
}
}
}
#IBAction func buttonMoveToSectionTapped(_ sender: UIButton) {
// if the sender's tag is row index
// or you can get indexPath by tableView.indexPath(for: cell) function too
let row = sender.tag
// move this item from section 1 to section 0 (last position)
if let item = items?[1].remove(at: row) {
items?[0].append(item)
}
// Save all sorting
saveSorting() { "\($0)" }
tableView.reloadData() // refresh table view
}
override func numberOfSections(in tableView: UITableView) -> Int {
return self.items?.count ?? 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items?[section].count ?? 0
}
}

Resources