I am using a UITableView to display a couple of sections which represent a to-do item, each with a couple of rows, which represent a smaller to-do item:
- Finish thesis
-- Interview representatives
-- Rewrite chapter 8
-- Create cover sheet
- Clean the house
-- Do the dishes
-- Mop living room
-- Clean windows in bathroom
I have successfully implemented a way to rearrange the rows by using the DragDelegate and DropDelegate of UITableView. I make sure everything is moveable (protocol canMoveRowAt returns true). To give you an idea of how I do this:
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
let toDoName = toDoList[indexPath.section].toDoItem[indexPath.row].toDoName
let itemProvider = NSItemProvider(object: toDoName as NSString)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
In MoveRowAt() I make sure the data is changed accordingly, removing the data from the sourceIndexPath and inserting it in the destinationIndexPath. This works great.
However, I would like to make sections draggable as well - to sort the to do list on priority. For example, if I add a new to do item, it automatically is appended to the bottom, but maybe this is something important that needs to be done first, so I want it - as a section with all its rows - to be dragged up if needed.
Can I accomplish this with the same method? I have figured out that there are no protocols for sections as there are for rows (moveRowAt exists, but moveSectionAt does not, same for canMoveSectionAt).
I have found a function, tableView.moveSection(section, toSection: section), which I will probably need to use. But how would I implement the drag & drop and attach it to the header of the section? Do I need to use a custom UILongPressGestureRecognizer? Any tips or available libraries on how to accomplish this?
I don't know if it's possible to drag and drop an entire section, but probably not, right now the API can consents to drag and drop row; one thing you could do is to create a CollectionView with a tableView in every Item, so you can drag and drop the Item containing your tableView
Related
I have UITableView and UITableViewCell.
I get data from API. Some items have a link, others have not.
If the item has not to link I wand to hide button with a book icon.
When I use this method (look below) button is hidden right, but then when the tableview reuse this cell icon with a book does not come back. How I can fix it?
var addButtonTrailingConstraint = openPdfButton.widthAnchor.constraint(equalToConstant: 0)
if link == nil{
NSLayoutConstraint.activate([addButtonTrailingConstraint])
}else{
NSLayoutConstraint.deactivate([addButtonTrailingConstraint])
}
}
This is kinda hard to answer without more code / knowledge of your constraint setup.
But I can give you 2 tips how to solve this issue by taking another approach:
1. Approach: Use UIStackView to manage your buttons:
Remove your buttons and replace them with a UIStackView. Then in code, where you config your cell (set text, title, ...) you first remove all Buttons from the UIStackView (you can do this easily with stackView.removeAllArrangedSubviews(), this is needed because the cells are getting reused and you don't want to add more and more buttons every time the cell is getting displayed.
After that, add the buttons you need in this cell (e.g.: like this: stackView.addArrangedSubview(button)).
This approach has the benefit that it is very dynamic, you can add as many different buttons as you wish without having to modify your code.
But since you need to create new buttons all the time it is not the most performance efficient solution.
2. Approach: Use 2 different UITableViewCell classes:
Make 2 different UITableViewCells, one with one button and a second one with 2 buttons. You can also inherit one from another to reduce duplicate code.
Then in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) check which one of the 2 cell classes you need, create the right one and set its members (text, title, ...).
This approach is less flexible but more performance efficient in comparison to the 1. approach.
I use both approaches in production and they are working quite nicely :)
You need
if link == nil {
openPdfButton.widthAnchor.constraint(equalToConstant: 0).isActive = true
} else {
openPdfButton.constraints.forEach {
openPdfButton.removeConstraint($0)
}
}
I designed a UITableView containing 3 different cell prototypes. I'm trying to figure out how I can capture their user inputs. One of them has a checklist in the form of a nested table view, so I need the ones they selected. Another has a UIPickerView so I need the selected option from a PickerView. Last one is a text area so I need the string inputting in that.
So I not only need a mechanism to capture the data to repopulate it back with their inputs when the user scrolls up or down to make it visible again. Would the best option be to use the following delegate function to capture non-visible cells:
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// do something with the cell before it gets deallocated
}
and then capture data on the visible cells on form submission? I'm not sure if this approach would work, so I'm wondering if there are better options.
The form input data would be 1 to many sections of self-repeating cells for sections 2 and up, so I need a way to capture all the input data with the TableView dequeuing these cells.
Your approach is wrong.
You have to implement a logic to update the data model at the moment the user changes something in the view.
This can be accomplished with protocol / delegate or with callback closures.
My question is this: Is this following approach to making a UILabel tap sensitive stylistically acceptable in the Swift 3 language? I'm tempted to say "if it compiles it flies" but I don't want to get in the habit of using this "short cut" if it is going to bite me later on. Further, if it is acceptable, are there some drawbacks to using this method that aren't obvious to a newcomer like me?
Please note that I am not looking for a way to implement code, I have one. I am asking if the solution I have is acceptable from a language style perspective.
I've been trying to get a UILabel to accept a TapGesture when inside a table cell for 2 days now and whatever method I try, there is always some sort of error even if it will compile. On a hunch, I went to my storyboard for the table view and added a button on top (not stacked, or aligned, or anything like that – actually occupying the same 2D space on the story board) within the prototype cell. I deleted the button text, linked it to the table view cell code and implemented some basic functionality to change the text on the UILabel to red and back. All of this functions exactly like I expected. I click the button and the text changes from black to red, and back when clicked again. The UILabel text is static in the table cell on a white background and my real function isn't going to change the text, it is going to alter another view through delegation from that view.
Why do it this way? Even if I check the UILabel use interaction box and follow some of the other questions and answers here to make it tap-able, I cannot control+drag the UILabel to the table view cell and make an action, the option simply doesn't exist in the pull down menu. It is available if I control+drag the UILabel to the table view controller. This makes a kind of sense to me because it is the table view controller that senses touch (right?). But, on the other hand, I have a switch in the table view cell that works just fine when I follow the answer to this question. Simple functionality of the storyboard-code interation (control+drag) is preventing me from getting what I want. Maybe control+drag should be allowing me to make an action and doesn't? I don't know. I don't want to use a UIButton alone because the text scaling feature of UILabel is really handy.
If you just want to recognize taps on the table cell, make the class the delegate of the table view
tableView.delegate = self
and implement
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//your code here
}
I'd also appreciate you taking the time to read a bit more about UITableViews. Every UITableViewCell has a label by itself. Consider the following code:
tableView.dataSource = self // this code will ideally be in init
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "lovCell")
cell.textLabel?.text = dummyData[indexPath.row]
return cell
}
I'm trying to create an options page for my iOS app. I have an array of categories like this:
var options = [
"Location",
"Calculation Method",
"Juristic Method",
"Manual Adjustment",
"Daylight Saving Time"
]
Then I am loading them up in my view controller like this:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return options.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .Default, reuseIdentifier: "Cell")
cell.textLabel?.text = options[indexPath.row]
return cell
}
This give me a single table view perfectly! Now I'm trying to handle the click of each row to bring up a list of options for that category. For example, the Location will have a switch to enable GPS or allow them to select their location drop downs or a map. Calculation Method will bring up a table view of check marks. Daylight Saving Time will bring up a single row with a switch.
My question is what is the best approach for all this? Should I create a dictionary of arrays to hold my options and reuse the table, or should I create a separate view for each category of options? I'm finding conflicting or outdated tutorials on this and I'm also having trouble converting examples from Objective-C. Any help or direction would be greatly appreciated!
I'd suggest creating separate views for each category of options. From your example above, you'd end up with 5 more view controllers: Location, Calculation Methods, Juristic Method, Manual Adjustment, and Daylight Saving Time. And then for your Calculation Methods, since it's a table view of check marks, I'd probably have an array that stores each option inside the CalculationMethodsViewController. Same for the others. If they need other data to display, put it in the new view controllers - not in your original view controller.
For your top level of categories, I'd suggest using the sections of a tableview to separate things. Then each row in a section would correspond to the detailed option.
To do this, you could have an array of 'categories' that are to keep the order of sections. This would match the options array you've defined in your question. Alongside this, I would put a Dictionary of sub-category options. For Locations it would look like this:
var options = [
"Location" : ["location-enable-gps", "location-choose-list", "location-choose-map"],
"Calculation Method": ...,
"..."
]
In the above Dictionary, the array values for Location are constants that I've defined. This is because you will be changing the behavior of each cell drastically. Enable GPS can just be a checkbox, Choosing from a list may be better to drilldown/modal (and when selected display selected location), choosing from map may also lend itself to a modal design ("Modal" is when a view controller pops up and then dismisses after an action).
Let me know if this all makes sense, it looks like your options will be very diverse, so unfortunately I don't think there is one method (drill down, collection view) that works best. That being said, its all up to you on how you want your users to experience your app.
firstly thank you in advance for any help and secondly I am very new to do the iOS development and swift so please bear with me and forgive me if I ask any stupid question.
MY ISSUE:
I created a table view and created an array of numbers. I iterated through the array and displayed each number in the array in a different table view cell. Now what I want to do is either have a button my table view cell or a check mark. And when ever I tap the button or the check mark the number that is being displayed on the table cell from the array is selected and then I tap another button or a check mark on a different cell and that number also gets selected and when I click the "done" button the both numbers are added and displayed on my root view.
Its kind of like a order taking app you can think of each cell as displaying the price of a food item and you can selected multiple food items and once you click done it adds up the price and displays it.
Im not looking for any advanced techniques, anything that can help me do this will be much appreciated. Again sorry for any stupid questions and thank you.
This is how you should break it down:
1) Implement the didSelectRowAtIndexPath delegate method after the table has been populated:
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
2) Within that method set the checkmark for the row:
cell.accessoryType = UITableViewCellAccessoryType.Checkmark
3) Declare a class Array:
var numberArray: [Int]
4) Finally, implement an array extension for the addition. Call it when tapping the done button:
How can we create a generic Array Extension that sums Number types in Swift?
There's some fine tuning depending on your implementation but I hope this gives you a head start.
Key things to look at:
UITableView has a var called allowsMultipleSelection. You want to set that to true (it's false by default.)
If you want to change the way a cell looks when it is selected, then you can either look into the table view's delegate didSelectRowAtIndexPath and didDeselectRowAtIndexPath. Or if you have your own subclass of UITableViewCell, you can override the setSelected:animated: method.
The basic idea is to give the user a button to say when (s)he is done selecting cells, then look at the tableView's indexPathsForSelectedRows to find out which items were selected.