Expandable tableView cell visible issue for bottom row? - swift

I have the tableview cell with expandable row its working fine. Now I have the issues on when bottom cell of the table view row it will expand but user need move to tableview for viewing expandable part. how to do it will move automatically?

I think you are reloading the section after expand/collapse the 'tableView' sections.
You can scroll the tableView to the section rect you are expanding.
For ex:
let sectionRect = tableView.rect(forSection: section)
tableView.scrollRectToVisible(sectionRect, animated: false)
Hope this helps!

Better use the func scrollRectToVisible(_ rect: CGRect, animated: Bool) this method does not scroll the tableview if the section is visible. You can easy to get the section rect using this method func rect(forSection section: Int) -> CGRect
example:
tableView.performBatchUpdates({ [weak tableView] in
tableView?.insertRows(at: indexPaths, with: .fade)
}, completion: { [weak tableView] _ in
if let sectionRect = tableView?.rect(forSection: section) {
tableView.scrollRectToVisible(sectionRect, animated: true)
}
})

Related

how to show the middle cell of a collection view when my app starts

I have a collectionView embedded in a subview of the ViewController. The collectionView has 12 cells. Each cell takes up the whole width and height of the collection view, so that I can achieve the pagination affect. However, when the app starts, I want to show the middle cell like the 6th or 7th one of my collectionView.
P.S. I have the collectionView in a wrapper view, not in my viewController.
In my WrapperView, I added the following method but as this method is called after the collectionView is added, it shows a sudden jump.
override func didAddSubview(_ subview: UIView) {
super.didAddSubview(subview)
let indexPath = IndexPath(row: 6, section: 0)
DispatchQueue.main.async {
self.calendarCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
}
}
If I could do it before the collection view appear on the screen, I may be able to fix that problem, but I can't find which method is called before the didAddSubview(_:) method in UIView Life Cycle.
Can anyone give me hint on how to solve this.
After your data source has loaded use:
func selectItem(at indexPath: IndexPath?,
animated: Bool,
scrollPosition: UICollectionView.ScrollPosition)
https://developer.apple.com/documentation/uikit/uicollectionview/1618057-selectitem
Probably best used before the view appears so trigger in viewWillAppear or viewDidLoad

Swift tableView scroll to row gives a wrong value for the content Offset value of my tableview

In my application I have a tableview in which I am showing approximately 400 products. The problem I am facing is that while scrolling to the beginning of a section programmatically like so:
#objc func CategoryPress(_ button: UIButton) {
let indexPath = NSIndexPath(item: 0, section: button.tag)
tableView.scrollToRow(at: indexPath as IndexPath, at: UITableViewScrollPosition.top, animated: false)
}
I am getting a wrong value for my tableView.contentOffset.y that stays incorrect until I reach the top of my tableview again. This issue happens only when scrolling to a position far from my start position.
While scrolling manually I am not facing this issue and all my values are exact.
I tried to scroll to a specific y on the button press but I didn't find a way to do this.
Any Solution or alternative to get the specific position would be highly appreciated.
I found a working alternative which is:
#obj func CategoryPress(_ button: UIButton) {
let indexPath = NSIndexPath(item: 0, section: button.tag)
var offset: CGPoint = tableView.contentOffset
offset.y = CGFloat(Ycoordinates[indexPath.section])
tableView.setContentOffset(offset, animated: true)
}

UITableViewController: Scrolling to bottom with dynamic row height starts animation at wrong position

I have a table view properly configured to have dynamic row heights based on Ray Wenderlich's guide found here:
I set up the constraints to have a clear line of constraints from the top to the bottom of the cell. I also set up content hugging and content compression resistance priorities and estimated row height.
This is the code I use to setup the table view:
func configureTableView() {
// its called on viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100.0
}
override func viewDidLoad() {
super.viewDidLoad()
configureTableView()
for i in 1...20 {
messages.append([
"title": "foo \(i)",
"message": "bla \(i)\nbla\nbla"
])
}
// this is because the actual row heights are not available until the next layout cycle or something like that
dispatch_async(dispatch_get_main_queue(), {self.scrollToBottom(false)})
}
func scrollToBottom(animated:Bool) {
let indexPath = NSIndexPath(forRow: self.messages.count-1, inSection: 0)
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: animated)
}
And this is how I add new rows:
#IBAction func addMore(sender:UIBarButtonItem) {
let message = [
"title": "haiooo",
"message": "silver"]
messages.append(message)
let indexPath = NSIndexPath(forRow: messages.count-1, inSection: 0)
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Bottom)
scrollToBottom(true)
}
The setup with the default rows are fine. It add the rows and scrolls to the bottom as expected.
But when I add new rows after that, the scrolling seems to start above the last cell. As I add more cells, the offset seems to increase.
Here is a gif showing it happening: Imgur
It's certainly related to the scroll animation (not the insertRow animation) because it scrolls properly when the animation is turned off.
Changing the estimatedRowHeight makes difference on the scrolling offset, but I couldn't find a value that fixed it.
I also tried delaying up the scroll using dispatch_async but it didn't change anything.
Do you guys have any ideas?
Wow, that was a fun challenge. Thanks for posting the test project.
So it seems that after adding the new row there's something off with where the table view thinks it's scrolled to. Seems to me to be a bug in UIKit. So to work around that, I added some code to 'reset' the table view before applying the animation.
Here's what I ended up with:
#IBAction func addMore(sender:UIBarButtonItem) {
let message = [
"title": "haiooo",
"message": "silver"]
messages.append(message)
tableView.reloadData()
// To get the animation working as expected, we need to 'reset' the table
// view's current offset. Otherwise it gets confused when it starts the animation.
let oldLastCellIndexPath = NSIndexPath(forRow: messages.count-2, inSection: 0)
self.tableView.scrollToRowAtIndexPath(oldLastCellIndexPath, atScrollPosition: .Bottom, animated: false)
// Animate on the next pass through the runloop.
dispatch_async(dispatch_get_main_queue(), {
self.scrollToBottom(true)
})
}
I couldn't get it to work with insertRowsAtIndexPaths(_:withRowAnimation:), but reloadData() worked fine. Then you need the same delay again before animating to the new last row.

Custom expanding/collapsing Date Picker expands off view when it's in the bottom cell of table view - Swift

I have a custom inline date picker that expands and collapses just like the Apple Calendar events date pickers. The problem is that I need the Date Picker to in a UITableViewCell that is at the bottom of the UITableView, but when it expands, you cannot see the Picker without scrolling the view down manually. For a similar issue (UITextField disappears behind keyboard when it's being edited if it's below the height of the keyboard) I was able to fix it with an animation in the beginEditing and endEditing delegate functions:
let keyboardHeight: CGFloat = 216
UIView.animateWithDuration(0.25, delay: 0.25, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
self.view.frame = CGRectMake(0, (self.view.frame.origin.y - keyboardHeight), self.view.bounds.width, self.view.bounds.height)
}, completion: nil)
Does anyone know of a similar animation that would work for maybe placing in didSelectRowAtIndexPath, and if it's a date picker the screen will adjust by scrolling down to show the full date picker expanded, and then it will adjust back when the row is not selected anymore - or something along these lines?
I'm not sure if the fact that there's already an animation occurring to expand/collapse the date picker would conflict with this or if it's just a matter of precedence for the animations to occur. Anyways I will post some code below:
When the DatePicker is selected in the tableView:
/**
Used to notify the DatePickerCell that it was selected. The DatePickerCell will then run its selection animation and expand or collapse.
*/
public func selectedInTableView() {
expanded = !expanded
UIView.transitionWithView(rightLabel, duration: 0.25, options:UIViewAnimationOptions.TransitionCrossDissolve, animations: { () -> Void in
self.rightLabel.textColor = self.expanded ? self.tintColor : self.rightLabelTextColor
}, completion: nil)
}
didSelectRowAtIndexPath function:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Deselect automatically if the cell is a DatePickerCell.
let cell = self.tableView(tableView, cellForRowAtIndexPath: indexPath)
if (cell.isKindOfClass(DatePickerCell)) {
let datePickerTableViewCell = cell as! DatePickerCell
datePickerTableViewCell.selectedInTableView()
tableView.deselectRowAtIndexPath(indexPath, animated: true)
} else if(cell.isKindOfClass(PickerCell)) {
let pickerTableViewCell = cell as! PickerCell
pickerTableViewCell.selectedInTableView()
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
tableView.beginUpdates()
tableView.endUpdates()
}
I think I came up with a solution that seems to work as needed, I wrote this code in the didSelectRowAtIndexPath delegate function after the call to selectedInTablView():
if(datePickerTableViewCell.isExpanded() && datePickerTableViewCell.datePicker == billDatePicker.datePicker) {
tableView.contentOffset = CGPoint(x: 0, y: self.view.frame.height + datePickerTableViewCell.datePickerHeight())
}

UITableView Pagination

My app has custom UITableView cells. I want to show only one cell at a time - next cell should show partially. In ScrollView you can set isPagingEnabled to YES.
But how can i do above in UITableView?
Thanks
Note that UITableView inherits from UIScrollView, so you can set pagingEnabledto YES on the table view itself.
Of course, this will only work if all cells and the table view itself are of the same height.
If you want to always have a cell start at the top of the table view after scrolling, you could use a UIScrollViewDelegate and implement something like this.
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
withVelocity:(CGPoint)velocity
targetContentOffset:(inout CGPoint *)targetContentOffset
{
UITableView *tv = (UITableView*)scrollView;
NSIndexPath *indexPathOfTopRowAfterScrolling = [tv indexPathForRowAtPoint:
*targetContentOffset
];
CGRect rectForTopRowAfterScrolling = [tv rectForRowAtIndexPath:
indexPathOfTopRowAfterScrolling
];
targetContentOffset->y=rectForTopRowAfterScrolling.origin.y;
}
This lets you adjust at which content offset a scroll action will end.
Swift 5 basic version, but it does not work that well. I needed to customize it for my own use to make it work.
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
if let tv = scrollView as? UITableView {
let path = tv.indexPathForRow(at: targetContentOffset.pointee)
if path != nil {
self.scrollToRow(at: path!, at: .top, animated: true)
}
}
}
Customized version
// If velocity is less than 0, then scrolling up
// If velocity is greater than 0, then scrolling down
if let tv = scrollView as? UITableView {
let path = tv.indexPathForRow(at: targetContentOffset.pointee)
if path != nil {
// >= makes scrolling down easier but can have some weird behavior when scrolling up
if velocity.y >= 0.0 {
// Assumes 1 section
// Jump to bottom one because user is scrolling down, and targetContentOffset is the very top of the screen
let indexPath = IndexPath(row: path!.row + 1, section: path!.section)
if indexPath.row < self.numberOfRows(inSection: path!.section) {
self.scrollToRow(at: indexPath, at: .top, animated: true)
}
} else {
self.scrollToRow(at: path!, at: .top, animated: true)
}
}
}
I don't think I'd use a UITableView for this at all.
I think I'd use a UIScrollView with a tall stack of paged content. You could dynamically rebuild that content on scrolling activity, so you mimic the memory management of UITableView. UIScrollView will happily do vertical paging, depending on the shape of its contentView's frame.
In other words, I suspect it's easier to make a UIScrollView act like a table than to make a UITableView paginate like scroll view.