Updating content size height - ios

I have a UIScrollView consisting of a UITableView tucked below a UIView. The UITableView has cells of dynamic height, and I'm having trouble setting the contentSize of the scroll view. If I set it in viewDidLayoutSubviews, it works fine, but calling it from viewDidLoad won't work. Calling it from viewDidLayoutSubviews is a problem though because that function is refreshing every few seconds, and I'm not sure how to change the contentSize of the scrollView if I add a new element to the tableView. This is what I have so far:
DispatchQueue.main.async {
UIView.animate(withDuration: 0, animations: {
self.tableView.layoutIfNeeded()
}) { (complete) in
print(self.tableView.contentSize.height)
self.tableViewHeightConstraint.constant = self.tableView.contentSize.height
self.scrollView.contentSize.height = self.tableView.contentSize.height + self.view.frame.width/2 + 10
print(self.scrollView.contentSize.height)
}
}
So my question is really, how can I make it work from viewDidLoad?

You can use ( Dispatch.main.async After ) to call it from viewDidLoad , or you set it in viewDidLayoutSubviews and put resize code in if statement with once bool value to be executed one time by setting once = false inside the IF statement

Related

UITextview text not centering vertically on initial view

I have an extension for UITextView which centers the text on a UITextView vertically that I found in the following SO answer: https://stackoverflow.com/a/38855122/4660602
My UITextView lives within a UITableViewCell, and the problem is that the function doesn't seem to work on the initial load. It only works when I reload the tableView or when I scroll.
I am calling the method within cellForRowAtIndexPath but I have tried adding it to my custom cell class in awakeFromNib and prepareForReuse but have and no luck. Wondering if anyone has any other advice / solutions.
EDIT:
Also forgot to mention, my VC with the tableView is embedded in a navigationBar and tabBar. When I switch to a new VC in the tabBar and then back, the UITextView text realigns to the top incorrectly, even if it was centered already.
Thanks!
The correct place for sizing code in a view is UIView.layoutSubviews. Since your centering function depends on the bounds, you have to call it in layoutSubviews, otherwise the bounds may not be correct (ie they match whats in the nib and not the current device). You can call setNeedsLayout in your cellforRowAtIndexPath to tell the view to update its layout after you ahve set the text.
In iOS 10, a bound of view hasn't initialized in viewDidLoad or awakeFromNib as before, so functions based on the view's bound don't work in viewDidLoad or awakeFromNib.
You should use it in viewDidAppear or when view's bound has certainly been initialized.
Works for me (Swift 5):
class VerticallyCenteredTextView: UITextView {
override var contentSize: CGSize {
didSet {
var topCorrection = (bounds.size.height - contentSize.height * zoomScale) / 2.0
topCorrection = max(0, topCorrection)
contentInset = UIEdgeInsets(top: topCorrection, left: 0, bottom: 0, right: 0)
}
}
}
The source - https://geek-is-stupid.github.io/2017-05-15-how-to-center-text-vertically-in-a-uitextview/

Make UITableView Header "stickier", harder to pull down

I have added header with custom view to my TableView. It is used as a search bar, I set contentOffset so it is hidden in beginning.
Everything works as expected but I am wondering if there is any way to make it harder to pull down? (should be stickier) Now it opens with just regular scrolling which is too easy.
EDIT: ScrollViewDidScroll Code
if tableView.contentOffset.y < 80 && tableView.contentOffset.y > 40 {
subscriptionsTableView.contentOffset.y += 0.23
print("Content offset")
print(tableView.contentOffset.y)
}
I had a similiar problem and solved in quite easily in the end. I set the table view top inset to the height of the header
tableView.contentInset.top = -1 * headerView.frame.size.height
This makes the header be shown "off screen", the user is now not being able to scroll to it. I added a code to change the top inset to 0, showing the header, when the user scrolls to the top over the header
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y < 0 {
UIView.animate(withDuration: 0.25, animations: {
self.tableView.contentInset.top = 0
})
} else if scrollView.contentOffset.y > headerView.frame.size.height {
UIView.animate(withDuration: 0.25, animations: {
self.searchBar.resignFirstResponder()
self.tableView.contentInset.top = -1 * self.headerView.frame.size.height
})
}
}
I also set the top inset back when the user scrolls the table view.
My 2 cents: observe the contentOffset property of the table view (or implement the scrollViewDidScroll: method) and adjust the position of the view you want to stick.
Here is a reference to something that uses the same principle:
make UIView in UIScrollView stick to the top when scrolled up
Is it possible to add fixed content to a UIScrollView?
Make static UIView sticky when UIScrollView is scrolling

UITableView ContentSize when using dynamic height on UITableViewCells

I've been searching for this for many hours but didn't find a way to make it.
I have a UITableView whose UITableViewCells use AutomaticDimension for Height:
tableView.RowHeight = UITableView.AutomaticDimension;
tableView.EstimatedRowHeight = 160f;
The problem is when I try to get tableView's ContentSize. It seems to be calculated based on EstimatedRowHeight instead of the current height of its rows. Suppose, if there are 10 Cells then ContentSize's returned value is 160x10.
Then my question is if there is a way to do this.
Any help will be really appreciated... I use Xamarin.iOS but Obj-C and Swift answers are obviously welcome :)
I think #Daniel J's answer only works because of the way the animation completion handler is scheduled - on the face of it, there is no guarantee the table reload will have happened before the animation completes.
I think a better solution is (in Swift, as that is what I was using):-
func reloadAndResizeTable() {
self.tableView.reloadData()
dispatch_async(dispatch_get_main_queue()) {
self.tableHeightConstraint.constant = self.tableView.contentSize.height
UIView.animateWithDuration(0.4) {
self.view.layoutIfNeeded()
}
}
}
I actually used the animation delay because of the effect I wanted, but it does also work with a duration of zero. I call this function every time I update the table contents and it animates in and resizes as necessary.
I had a scenario where I needed to know the contentSize as well, to set the frame on a non-fullscreen tableView that had varying cell counts of varying heights. Then I'd adjust the tableView (via NSLayoutConstraint) to be the height of that content.
The following worked, though I feel it's a bit hacky:
Setup all code in 'viewDidLoad'
Set the rowHeight to automatic
Set the estimatedRowHeight to larger than I expected a cell to ever be (in this case, I didn't expect a cell to ever be taller than about 60pts, so set the estimated to 120)
Wrap the tableView reload in an animation block, and then query the contentSize and take appropriate action in the completion block
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 120.0;
[UIView animateWithDuration:0 animations:^{
[self.tableView reloadData];
} completion:^(BOOL finished) {
self.tableViewHeight.constant = self.tableView.contentSize.height;
[self.view layoutIfNeeded];
}];
}
self.coins = coins
tableView.reloadData()
view.layoutIfNeeded()
tableViewHeightConstraint.constant = tableView.contentSize.height
view.layoutIfNeeded()
worked without UITableViewAutomaticDimension and estimatedRowHeight. iOS 11
tableView.reloadData()
view.layoutIfNeeded()
tableViewHeightConstraint.constant = tableView.contentSize.height
view.layoutIfNeeded()
Before getting tablaview.contentSize.height
reload tableView
You need to update the view.

Animate subview on UIViewController view

I have UITableView as a subview over UIView (parent view) of UIViewController. When i'm animating my parent view as below:
UIView.animateWithDuration(duration, animations: {
viewController?.view.frame = CGRectMake(0,0,320,568)
}, completion: {_ in
})
I observed that my subview (table view) is not animating. I tried setting Autoresizing Mask of subviews to flexibleWidth and flexibleHeight, but din't get any success. Anybody having any idea why its happening.
If you are using auto layout (which is the default in iOS 8/9 on Xcode 6/7) you need to change the constraints of your view inside he UIView animation closure, instead of changing the frame.
If you are not using auto layout, then you can update the frame as above, however, I'd imagine you are. Thus you need to call layoutSubviews, then programatically set new constraints that represent your desired change to the layout, and finally call layoutSubviews again, inside the animation closure.
Example:
func AnimateBackgroundHeight() {
self.view.layoutSubviews()
UIView.animateWithDuration(0.5, animations: {
self.heightCon.constant = 600 // heightCon is the IBOutlet to the constraint
self.view.layoutSubviews()
})
}

ScrollView contentOffset reset after pushViewController

I'm experiencing a problem with ScrollView, my scrollView has 640 of width, from 0 to 320 it's a mapView, from 320 to 640 it's a tableView.
I have a segmentedButton with 2 options to switch my view from contentOffset from 0 to 320 in x.
The problem is, if I switch to contentOffset of 320 in x and press a cell of tableView, after releasing the button to push other viewController, the contentOffset of my scrollView is reseting to 0 in x. I need to keep contentOffset on the tableView, at 320 in x.
I tried many things, like playing around with viewWillDisappear and the lifecycle methods.
The closest I've been able to reach was making it going back to the tableView after pressing back. Although, I couldn't make it stay on the tableView to push the next viewController.
Any help is appreciated!
(Ps: I've search for similar questions, but the only one that could help, I couldn't understand very well, the others have similar but different problems).
Thanks
I had the same problem.
Here is the solution:
1. I'm doing content offset backup in viewWillDisappear
2. Restoring offset without animation in viewWillAppear.
UPDATE: Be sure you are not adjusting contentSize in viewWillLayoutSubviews or in methods mentioned above.
I have very similar problem with UItableView which is also UIScrollView, but vertical.
Playing with viewWillDisappear: does not work because there is a noticeable scroll made by iOS when user initiated a push of another VC.
Can't resolve this problem as well.
Saving contentOffset backup at viewWillDisappear is not the correct way of handling which make you even worse to handle if you have some views which required show/hide on viewWillAppear.
The best way is saving the contentOffset at scrollViewDidScroll
Declare the variable where you want to save offset
var previousOffsetY: CGFloat = 0.0
var offsetLimitation: CGFloat = 50.0 // is the limit where I want to play show/hide
Inside UIScrollViewDelegate which is scrollViewDidScroll, save the offset.
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
previousOffsetY = scrollView.contentOffset.y
UIView.animate(withDuration: 0.5, animations: {
if offset <= self.offsetLimitation {
// show
} else {
// hide
}
})
}
}
At viewWillAppear, control the UI you want when offset is reseted.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if previousOffsetY > offsetLimitation {
// hide
}
}
check your scrollview frame when push to a new controller, if scrollview frame changed, scrollview will reset it's contentOffset

Resources