ScrollView with dynamic textview as subview - ios

I want to fit a textView in a scrollView. As user types in the textView, the textView should expand in height. Everything seems to work fine until the textView reaches the height of the scrollView's height. The scrollView's contentSize is not updated. Here's my setup.
--scrollView
|--contentView
|--textView
The constraints are set as follow:
ScrollView - top, leading,trailing to the superview and bottom to bottomLayoutGuide
ContentView - top, leading, trailing, bottom to superview (ScrollView), align center x and align center y to superview (ScrollView)
TextView - top, leading, trailing to superview (ContentView), bottom is <=400 points to superview (ContentView)
In specific, I do not set a height for textView so it can resize dynamically as user types. I am using autolayout and textView is also set to scroll disabled.
I tried to force an update to the contentSize on TextViewDidChange delegate call.
func textViewDidChange(_ textView: UITextView) {
let size = CGSize(width: textView.frame.width, height: CGFloat(MAXFLOAT))
let value = textView.sizeThatFits(size)
let newHeight = value.height + textView.frame.minY
if newHeight > scrollView.contentSize.height {
contentView.frame = CGRect(x: 0, y: 0, width: textView.frame.width, height: newHeight)
scrollView.contentSize = CGSize(width: textView.frame.width, height: value.height + textView.frame.minY)
}
}
There's couple of issues with this approach. First will be the sizeToFit is called on every keystroke. And textView seems "slow down" the more text I typed to the textView.
Another issue is as soon a I rotate the screen, the scrolling of the scrollView stops working.
Can someone shed some light on this?
* update *
I was able to get it to a state where the scrollView recognize the dynamic height change of the textView.
My new constraints are like this:
ScrollView - top, leading,trailing to the superview and bottom to bottomLayoutGuide
ContentView - top, leading, trailing, bottom to superview (ScrollView), width to scrollView's superview
TextView - top, leading, trailing to superview (ContentView), bottom is =400 points to superview (ContentView)
Now this does update the contentSize accordingly and I don't need to calculate any frames. However this leads to another issue where the bottom constraint is ALWAYS 400. Ideally I only want the bottom constraint to be 400 on init. And as the textView expands, it should decrease to it reaches 0 (textView bottom reaches the bottom on the contentView)
Does anyone know how to go about this?

Related

Swift -how is a collectionView's scrollView.content.y determined?

I have a collectionView with 5 cells. After I drag the collectionView down using this scrollView method I can get the following:
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let currentOffset = scrollView.contentOffset.y // 92 ???
print("currentOffset: \(currentOffset)")
let contentHeight = scrollView.contentSize.height // height of all the cells added together -616.310
print("contentHeight: \(contentHeight)")
let frame = scrollView.frame.size.height // height of the collectionView itself from it's topAnchor to it's bottomAnchor - 725
print("frame: \(frame)")
let maximumOffset = contentHeight - frame // -108.689453125 // distance between contentHeight (bottom of all the cells) and the frame
print("maximumOffset: \(maximumOffset)")
}
The contentHeight is the height of all the cells added together which is: -616.310
The frame is the height of the collectionView itself in between it's topAnchor and it's bottomAnchor which is: 725
The maximumOffset is the distance between contentHeight (bottom of all the cells) and the frame which is: -108.689453125
What I can't figure out is where does the currentOffset which is 92 comes from.
This isn't very clear:
contentOffset
The point at which the origin of the content view is offset from the
origin of the scroll view.
After dragging the collectionView, how is the currentOffset determined?
In the picture below at the bottom is the debugger with the print statements from the scrollViewDidEndDragging method
Not sure if this makes any difference or not but the collectionView's topAnchor and bottomAnchor are pinned to the view's safeAreaLayoutGuide ..., constant: 0 or it's anchored directly to it's top and bottom
It's how far the scrollView's top left hand corner (0,0) is from the top of the collectionView's top left hand corner (0,0) after the the collectionView is first dragged and let go.
So it was initially pulled up 92 points (0,-92) then let go.

Autoresizing UITableViewCell with UITextView which has text aligned to vertical center simultaneously?

Cell contains multiline text so I decided to use text view.
textView.textContainerInset = UIEdgeInsets.zero removes extra padding. Code to center it vertically:
extension UITextView {
func centerVertically() {
var topCorrect = (self.bounds.size.height - self.contentSize.height * self.zoomScale) / 2
topCorrect = topCorrect < 0.0 ? 0.0 : topCorrect;
self.contentInset.top = topCorrect
}
}
This works if I predefine cell height in tableView(_:, heightForRowAt:).
But if I use UITableView.automaticDimension to resize the cell everything becomes broken.
How to solve this issue?
Use auto layout and pin leading, trailing, top and bottom, constraints of textview to the cell.
Disable scrolling in textview
Add a height constraint such that height is greater than or equal to a constant value (this is a min value that will show on the screen) for textview
User .automatic dimension
Try this and let me know if this worked out for you

Define correctly height for UIScrollView

I'm creating a application using Swift3, and i have difficult to define correctly height for UIScrollView, i'm using autolayout and create this structure:
UIScrollView
UIView // The container view
UIImageView // Constraint Top Edges = 20 in relation to UIView
UITextView // Constraint Top Edges = 40 in relation to UIImageView
UITextView // Constraint Top Edges = 20 in relation to UITextView
UIButton // Constraint Top Edges 30 in relation to UITextView
Currently, i'm using this logic to calculate UIScrollView height
override func viewDidLayoutSubviews() {
var scrollHeight : CGFloat = 0.0
for view in self.containerView.subviews {
view.layoutIfNeeded()
scrollHeight += view.frame.size.height
}
// Adding height for scrollview, according to the sum of all child views
self.scrollView.contentSize.height = scrollHeight
}
But, i can only get the views height, and them not consider the Constraints "margins", i would like know any way to calculate correct height for UIScrollView, adjusted according their content.
Close! Let's add the top margin for each view:
override func viewDidLayoutSubviews() {
var scrollHeight : CGFloat = 0.0
for view in self.containerView.subviews {
view.layoutIfNeeded()
scrollHeight += view.frame.size.height
//Add the margins as well to the scrollHeight
scrollHeight += view.layoutMargins.top
}
// Adding height for scrollview, according to the sum of all child views
self.scrollView.contentSize = CGRect(x: self.scrollView.contentSize.width, y: scrollHeight)
}
self.scrollview.contentsize.height = containerview.height;

Updating UICollectionViewFlowLayout when the height of my CollectionView changes during runtime

I have a MyCollectionView that has a constraint that is:
BottomLayoutGuide.Top = MyCollectionView.Bottom + 100
In my IB, but this constraint's constant changes according to the height of the keyboard
func keyboardWillShow(notification: NSNotification) {
let keyboardHeight = getKeyboardHeight(notification)
keyboardHeightConstraint.constant = keyboardHeight + 20
self.cardCollectionView.configureCollectionView()
self.cardCollectionView.reloadInputViews()
}
Where configureCollectionView is:
func configureCollectionView() {
self.backgroundColor = UIColor.clearColor()
// Create the layout
let space = 10.0 as CGFloat
let flowLayout = UICollectionViewFlowLayout()
let width = (self.frame.size.width) - 4 * space
let height = (self.frame.size.height)
let edgeInsets = UIEdgeInsetsMake(0, 2 * space, 0, 2 * space)
// Set top and bottom margins if scrolling horizontally, left and right margins if scrolling vertically
flowLayout.minimumLineSpacing = space
flowLayout.minimumInteritemSpacing = 0
// Set horizontal scrolling
flowLayout.scrollDirection = .Horizontal
// Set edge insets
flowLayout.sectionInset = edgeInsets
flowLayout.itemSize = CGSizeMake(width, height)
print(self.frame)
print(flowLayout.itemSize)
self.setCollectionViewLayout(flowLayout, animated: true)
// Always allow it to scroll
self.alwaysBounceHorizontal = true
}
Before the keyboard is shown, everything is fine and these are the values of the frame of MyCollectionView and it's CollectionViewCell
self.frame: (0.0, 75.0, 375.0, 492.0)
flowLayout.itemSize: (335.0, 492.0)
But after the keyboard is shown and configureCollectionView() is called which basically just resets the flowLayout for MyCollectionView everything breaks. And oddly, even though the height of MyCollectionView has decreased the console outputs says that it has actually increased.
self.frame: (0.0, 55.0, 375.0, 512.0)
flowLayout.itemSize: (335.0, 512.0)
Here is a screenshot after the keyboard has appeared:
As you can see, it seems like the CollectionViewCells are being clipped and it has lost it's initial alpha value. Moreover, after randomly scrolling the console outputs:
2016-04-29 09:43:24.012 DeckWheel[15363:2028073] the behavior of the UICollectionViewFlowLayout is not defined because:
2016-04-29 09:43:24.012 DeckWheel[15363:2028073] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
I know that this is due to the height of MyCollectionView changing but, I don't know why it is going wrong as everything works perfectly before the keyboard shows. For example, I can scroll randomly between the cells and if I get to the last cell it will automatically create a new one without any crashes.
I've tried MyCollectionView.invalidateLayout() and then callingconfigureCollectionView() to reset the flowLayout but nothing seems to work. What is the correct way to update MyCollectionView so that this does not happen?

UIScrollView Content Size with Autolayout

I am using UIScrollView with Auto-layout to change contentSenter code hereize value as following :
UIView
ScrollView
UIView (contentView)
In run time I am adding UITextView & UIImageView to conentView with constraint(Top,Bottom,Left,Right)
but the contentView size is not changing and UIScrollView contentSize also.
So what could be the problem ?
In run time I am adding UITextView & UIImageView to conentView with constraint(Top,Bottom,Left,Right)
Maybe you should add constraint (Top, Left, Height, Width),or (Top, Left, Right, Height)
If you persist in (Top,Bottom,Left,Right), ScrollView will keep the old content size, and make your UITextView & UIImageView (Height = 0, Width = 0).
Here's a neat way of updating scroll view's content size. It's Swift so you'll need a bridging header if you're working in Objective C.
extension UIScrollView {
func updateContentViewSize() {
var newHeight: CGFloat = 0
for view in subviews {
let ref = view.frame.origin.y + view.frame.height
if ref > newHeight {
newHeight = ref
}
}
let oldSize = contentSize
let newSize = CGSize(width: oldSize.width, height: newHeight + 20)
contentSize = newSize
}
}
Just call it on your scroll view's object whenever you add a child view to your scroll view.

Resources