To clarify, I have a UITableView which is constrained to occupy the entire view of a UIViewController, but whose cells must be inset by a specific distance from the edge. I was told previously that this could be accomplished by setting the table view's style to .insetGrouped, and this does seem to be the case, but I am struggling to properly set the layoutMargins. They seem to be "stuck" to (0,20,0,20) no matter what a specify in IB or code.
Post
Replies
Boosts
Views
Activity
bump
Did anyone ever find a solution to this problem?
I found a solution on stack overflow that suggests that one may override setEditing(_:animated:) on the table view to get this behavior, but I have yet to find success. In my case, setEditing is not being called when a swipe action is being performed.
You can work around this problem via property observers on the @IBOutlets, but that still leaves me with my concern.
Is the timing in which @IBOutlets are defined in the owner after a call to UINib.instantiate(withOwner:options:) specified? My assumption until now was that they were simply initialized during the call to UINib.instantiate(withOwner:options:), which seems to be mostly true in practice. But I have now run into a example where this is not the case, and I worry that my existing code might be relying on behaviors that aren't actually guaranteed 😅.
In my effort to reproduce an example, I think have figured out what was breaking.
I had overconstrained the height of a stackview with .fillEqually s.t. it was marginally taller than the combined height of its arranged subviews (off by 0.5 pt)
It seems UIKit was making up for that difference by stretching one of my seperator views (a descendant of an arranged subview). This is weird because they are constrained to a height of 0.5 with required priority.
If this is what is happening, is this a bug? Or is .fillEqually best-effort to some extent?
Thanks,
smkuehnhold
That's basically what I am trying to do.
Is this a valid way to acheive that?
override func willMove(toWindow newWindow: UIWindow?) {
if let newWindow = newWindow {
let pts = 1 / newWindow.screen.nativeScale // px / scalingFactor = pts
heightConstraint.constant = pts // constrain height to 1 px
}
}
@Claude31 The problem with this approach is that the cell remains deselected while performing the swipe action, which I do not want. Unless I am misunderstanding what you mean by at the end of the swipe. Where would you place this functionality? tableView(_:didEndEditingRowAt:)?
I see. However, I think that has the same problem. Sometime after tableView(_:didBeginEditingRowAt:) is called, the selected rows of the table view are unset. With your method, the selected rows are only reset when the action is performed. So there is a gap of indefinite length where the previously selected cells remain unselected. Also, what happens when a user chooses to not perform the action?
That's very reassuring to know :). The way I have it now, these owned subviews are passed deep into the view hierarchy. Which means it is likely, as you alluded, I am dropping the reference somewhere.
Appreciate the clarification,
smkuehnhold
Answered over in this thread. :)
you could also reselect just before return swipe.
This does not work, because tableView(_:leadingSwipeActionsConfigurationForRowAt:) and tableView(_:trailingSwipeActionsConfigurationForRowAt:) are called before the selected rows are unset.
in didSelectRow
I'm assuming you mean willDeselectRow/didDeselectRow? Unfortunately, it does not work becuase it does not seem the deselection caused by the swipe action calls these methods :(
But my main question is when do you re-apply the selection. Because doing it in tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) or didDeselectRow does not work for the above reasons.
I found the following example in the docs that maybe indicates that I was misusing the built-in functionality regarding cell selection (see Managing Selection Lists)?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Unselect the row.
tableView.deselectRow(at: indexPath, animated: false)
// Did the user tap on a selected filter item? If so, do nothing.
let selectedFilterRow = selectedFilters[indexPath.section]
if selectedFilterRow == indexPath.row {
return
}
// **
// NOTE: Why else would you "deselect" in didSelectRowAt?
// **
// Remove the checkmark from the previously selected filter item.
if let previousCell = tableView.cellForRow(at: IndexPath(row: selectedFilterRow, section: indexPath.section)) {
previousCell.accessoryType = .none
}
// Mark the newly selected filter item with a checkmark.
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
}
// Remember this selected filter item.
selectedFilters[indexPath.section] = indexPath.row
}
If that is the case, then really the only solution to my underlying question "how do I prevent UIKit from implicitly de-selecting my cells" I think is to mantain your own storage of "selected items" seperate from what is included in the table view (like what the example did with selectedFilters). At least this is what I ended up doing.
This doesn't really answer the original question, but I think my reason for asking this question was misguided.
@Claude31
I always appreciate to see your replies on this forum :)
For brevity, I hope you don't mind that I share a smaller example instead. Also, sorry for not replying as a comment. I could not fit an example.
protocol StoryboardBased: UIViewController {
// instantiates a view controller from a storyboard
// default impl based on UIStoryBoard(name:bundle:).instantiateViewController(withIdentifier:)
func makeFromStoryboard() -> Self
}
class ViewController: UIViewController, StoryboardBased {
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
override init(nibName nibNameOrNIl: String?, bundleName bundleNameOrNil: String?) {
super.init(nibName: nibNameOrNIl, bundleName: bundleNameOrNil)
_init()
}
private func _init() {
// init is the earliest time you can update the title correct?
// also doesn't work if I set in viewDidLoad(:) for reference
// or if I set directly from outside prior to instance being pushed
navigationItem.title = makeTitleFromDynamicData() // makeTitleFromDynamicData returns a (non-optional) String
}
}
// Parent is in Navigation Stack
class ParentViewController: UIViewController {
@IBAction func buttonTouchUpInside(_ sender: Any?) {
// push a view controller in response to the user pressing a button
navigationController.pushViewController(
ViewController.makeFromStoryboard() // instantiate Storyboard-based ViewController,
animated: true
)
}
}
Thanks,
smkuehnhold