iOS - Move TableView before scrolling - ios

I have a view with an image and a tableview. The image is in the first half of the screen (portrait) and the tableview in the second half. And now I want to move the tableview over the image until it reaches the top and covers the image. Then it should start the "real scrolling".
But what's the best way to do this? Can I like replace the touchesMove of the variable tableView? I could create an extension of the UITableView and override the function, but then I don't have access to the view of my Controller to move the tableView.
Any answer? Thanks!

The imageView should be behind the tableView with constraints top, leading, trailing to superview and height to superview with a multiplier of 0.5. The tableView should fill its superview.
The trick is that you add a tableViewHeader that is invisible and equal to half the height of the screen. This has the effect of pushing the initial content of the tableView off the screen. In interface builder add a UIView to the tableView as header and make it transparent. Also make the background of your tableView transparent. Take an outlet to the headerView and the tableView. In viewDidLayoutSubviews set your headerView.frame.size.height = tableView.frame.size.height / 2.

Apart from what Josh answered, what you can try is:
What you can do is make the UIImageView's height decrease based on the scroll amount until the UIImageView's height is 0 and then start the scrolling otherwise force the contentOffSet of the UITableView to always remain 0. That being said, here is how to do it:
Make an enum to keep track of the various states of the UIImageView like:
enum Layout {
case ImageExpanding,
case ImageDefaultHeight,
case ImageDiminishing,
case ImageNotVisible
}
In your storyboard, add a top, leading and trailing constraint to the superview for your UIImageView and fixed height constraint of lets say 200(don't worry you will change this later). To your UITableView add a leading, trailing and bottom constraint to the superview and a top the UIImageView. Now drag and drop a constraint outlet for the UIImageView height into your UIViewController.
In your viewDidLoad set your heightConstraint to be 1/2 of the total screen height and set the enum state to initially be ImageDefaultHeight
Now in your scrollViewDidScroll you will have to check the direction of the scroll and based on that while checking the current state of the image, increase or decrease the heightConstraint based on the amount a user scrolls by and always check:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if(/*up*/){
//just telling you about this if condition as this will ensure that
//if your table view is large and if tableview isn't at the top,
//the image shouldn't expand
if layout == .ImageNotVisible{
if scrollView.contentOffset.y <= 0 {
//start expanding once you reach top of tableview
layout = .ImageExpanding;
}
}
//keep track of other enum states and update your uiimageview
//appropriately after calculating the user scroll amount
//until the height reaches your initialDefaultHeight and
//then do nothing. You will have to figure out the code
//for this on your own
}else if(/*down*/){
//make image smaller
}
//dont let table view scroll until image height is 0 when use is scrolling down
if layout != ImageNotVisible{
scrollView.contentOffset = CGPoint(x: 0, y: 0)
}
}

Related

UITableView's height its content with Auto Layout but limit UITableView height

I have a UITableView and UIButton in a UIViewController.
UIViewController -> UIView -> UITableView
-> UIButton
The UITableView height is set to change accordingly to its intrinsic content size. When there are more number of row in UITableView the height is increasing but i don't want to increase such that UIButton should not be visible. I don't want to have UIScrollView either in that UIViewController. The constraint for UIButton is set as 25pixel from bottom, 25pixel from UITbaleView and width is set to 70% of screen width. I have constraint set for UITableView as 10pixel from top, width same as superview and height set to 590. I am not sure how to set the priority so that UIButton will be visible even if there are more number of rows in UITableView.
I am using below code to increase the height of UITableView
override func updateViewConstraints() {
tableViewHeight.constant = TableIb.contentSize.height
super.updateViewConstraints()
}
Constrain your button:
25-pts from tableView bottom
bottom lessThanOrEqualTo -25-pts from view bottom (safe-area)
Give your tableViewHeight constraint a Priority of less-than-required.
So, for example:
button.topAnchor.constraint(equalTo: tableView.bottomAnchor, constant: 25.0).isActive = true
button.bottomAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -25.0).isActive = true
// whatever you want to start your tableView height at...
// it doesn't really matter, as I assume you'll be immediately
// changing its .constant value
tableViewHeight = tableView.heightAnchor.constraint(equalToConstant: 30.0)
tableViewHeight.priority = .defaultHigh
tableViewHeight.isActive = true
The button will "stick" 25-pts from the bottom of the tableView, and when you change tableViewHeight.constant it will "push the button down" but only until it is 25-pts from the bottom of the view (the safe-area).
As a side-note, get into the habit of referring to Points instead of Pixels, and view width instead of screen width.
Alternatively, embed your button in a UIView and set that view as the viewForFooterInSection. It will "stick" to the bottom row in the tableView, but remain visible when the tableView needs to scroll.
That would allow you to avoid the need to keep re-sizing your tableView.

Resize ScrollView based on TableView size

I'm trying to create layout that it structured like this:
- View
-- ScrollView
--- ContentView
---- CustomView
---- CustomView
---- TableView
---- CustomView
The tableView itself is auto-resizable using "invalidateIntrinsicContentSize" and when I add items - the height of the tableview changes, pushing the custom view below it further down.
Once enough items are added I the bottom custom view is hidden and the scroll doesn't work.
important fact - the bottom custom view doesn't have a bottom constraint. It is pushed down by the it's top constraint to the tableView.
If I do set a bottom constraint - the table view will no longer be dynamically resized.
The intended behaviour:
When a user adds items to the list and the list gets too big the ContentView will be scrollable so the user can scroll to see the bottom view.
The actual behaviour:
When a user adds items to the list and the list gets too big, the bottom view is pushed down and outside of sight and content is not scrollable.
What is happening and how can I fix it?
Below is what I think what is happening.
Since you are using UITableView, it has its own scroll view. So when the UITableView list gets too big, UITableView itself becomes scrollable rather than ScrollView's contentView becoming scrollable.
To achieve what you need, you would have to make the UITableView not scrollable and use the intrinsicHeight of the UITableView to get the actual height of UITableView along with all the items. If you have items with varying heights, it will be a problem because you won't know the height before rendering. With same height for all the rows, you can get the total height of the UITableView and set the height constraint to that value. This will increase the contentSize of the outer ScrollView, making it scrollable.
Apart from UITableView, you can also use UIStackView. This is because you are not using the reusing capabilities of UITableView anyways. Managing the datasource and delegates should not be a big problem.
You can create a constraint for tableview height, And take its reference to your swift file, by dragging it as you take other views. Now in your code, Just do this
tableViewHeightConstraint.constant = tableViewNoOfItems * tableViewCellHeight;
if you have set other constraints perfectly inside scrollview, It should work perfectly. Means TableView should have top, bottom, left, right margined constraints from the ScrollView.
try this code
tblViewHeight.constant = CGFloat( tableview row count * 45 )
var size = contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
if size.height < scrollView.frame.size.height
{
size = scrollView.frame.size
}
contenViewHeight.constant = size.height - scrollView.frame.size.height
scrollView.contentSize.height = contenViewHeight.constant
What I think you could do is:
Disable tableView's scroll tableView.isScrollEnabled = false
Every time a user adds items to the list, reload the tableView
Also using UIStackView with vertical axis and .fillEqually distribution as a Content View would be much more convenient as you won't need to set any positional constraints to your views, but may need to set height constraints if intrinsic content size can't be determined by the engine

View at the bottom in a UIScrollView, with AutoLayout

I'm setting up content in a scroll view with autolayout. The objects in the scrollview are pinned top-to-bottom to the previous one, so that they are under one another. I have a footer view that is added at the end, below these objects.
Here's the catch: when there's few content, the contentView will be smaller than the screen height, so the footer view will appear somewhere in the middle of the screen (which is the normal behavior). But I'd like to prevent that, and make the view stay somewhere at the bottom.
In other words, I would like to setup a double constraint like:
Put this view below all the objects in the scrollview
AND
keep this view at a distance of max [some number] of the bottom of the screen
In a way that both constraints are always satisfied:
If the height of the content is bigger than the screen, then the view appears at the bottom, after scrolling down
If the height is smaller, then the view is "pinned" to the bottom of the screen, leaving a space relatively big between the bottom of the content and the top of this view
How can I achieve that with AutoLayout?
Fairly easy to do with Auto-Layout only... no code required.
The key is to use a "content view" to hold the elements, and a greater-than-or-equal constraint between your "bottom" element and your "footer" view.
In this image, yellow is the main view, green is the scroll view, blue is the content view, the labels are gray and the footer view is pink.
Start with a fresh view controller
add a scroll view, normal constraints (I used 20 all the way around, so we can see the frame)
add a UIView to the scrollView - this will be our "content view"
constrain contentView Top/Bottom/Leading/Trailing all equal to 0 to the scrollView
constrain both the Width and Height of the contentView equal to the scrollView
add your elements - here I used 3 labels
constrain the labels as usual... I used:
LabelA - Top/Leading/Trailing all at 20, vertical spacing to LabelB of 60
LabelB - Leading/Trailing at 20, vertical spacing to LabelC of 60
LabelC - Leading/Trailing at 20
LabelC is also set to Number of Lines: 0 so it will expand with multiple lines of text
Add a UIView as a "footer view" (I stuck a label in it)
constrain the footerView Leading/Trailing/Bottom all at 20 (so we can see the frame)
either set a Height constraint on footerView, or use its content to constrain its height
add a Vertical Spacing constraint from LabelC to footerView, and set it to >= 40
last step, change the Height constraint of contentView to Priority: 250
Now, as you expand/contract the height of LabelC, the footerView will keep at least 40-pts of vertical space. When LabelC gets big enough to "push" footerView below the bottom, scrollView will become scrollable.
Results:
you need to check ContentSize of scrollView and modify FooterView Top Constraint with the required Value
My class code
import UIKit
class scrollViewDrag: UIViewController
{
/// ScrollView Outlet
#IBOutlet weak var mainScrollView: UIScrollView!
/// Footer View top spacing constraint
#IBOutlet weak var footerViewTopConstraint: NSLayoutConstraint!
/// Used for ScrollView Height
var screenHeight = CGFloat()
/// Did Load
override func viewDidLoad() {
super.viewDidLoad()
}
/// Function used to check for height
func checkForHeight(){
/// Get scrollView Height
screenHeight = mainScrollView.frame.size.height
/// Check contentSize Height ?
if mainScrollView.contentSize.height >= screenHeight {
/// When ScrollView is having height greater than your scrollView Height
/// Footer will scroll along other Views
}
else{
/// Issue Case
let spacingValue = screenHeight-mainScrollView.contentSize.height
footerViewTopConstraint.constant = spacingValue
}
}
/// Call the height function in DidAppear
override func viewDidAppear(_ animated: Bool) {
checkForHeight()
}
}
Storyboard
I had used Four View with Equal Heights And at last a footerView is attached as Fourth View
FooterView Top Constraint
Top constraint used as footerViewTopConstraint
Output
Case 1 - Size is greater than scrollView Height
Case 2 - Expected Output

scrolling/resizing UITableView

I'll get right to the point.
I have a UIViewController that has two subviews in it. The top one (let's call it HeaderView from now one) is a custom UIView and the bottom one is a UITableView.
I have set them up in InterfaceBuilder so that the HeaderView has 0 margin from the left, top and right, plus it has a fixed height.
The UITableView is directly underneath with 0 margin from all sides.
My goal is to achieve a behaviour such that when I start scrolling the UITableView's content the HeaderView will start shrinking and the UITableView becomes higher without scrolling. This should go on until the HeaderView has reached a minimum height. After that the UITableView should start scrolling as normal. When scrolling down the effect should be reversed.
I have initially started this out using a UIScrollView instead of the UITableView and I have achieved the desired result. Here is how:
connect the UIScrollView to the outlet
#IBOutlet weak var scrollView: UIScrollView!
set the UIScrollViewDelegate in the controller's viewDidLoad() method
self.scrollView.delegate = self
and declared the UIViewController to conform to the protocol
intercept when the UIScrollView scrolls:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.adjustScrolling(offset: scrollView.contentOffset.y, scrollView: scrollView)
}
in my adjustScrolling(offset:scrollView:) method the "magic" happens
Now let's look at what happens in this method.
private func adjustScrolling(offset: CGFloat, scrollView: UIScrollView) {
// bind value between 0 and max header scroll
let actualOffset: CGFloat = offset < 0 ? 0 : (offset >= self.maxHeaderScroll ? self.maxHeaderScroll : offset)
// avoid useless calculations
if (actualOffset == self.currentOffset) {
return
}
/**
* Apply the vertical scrolling to the header
*/
// Translate the header up to give more space to the scrollView
let headerTransform = CATransform3DTranslate(CATransform3DIdentity, 0, -(actualOffset), 0)
self.header.layer.transform = headerTransform
// Adjust header's subviews to new size
self.header.didScrollBy(actualOffset)
/**
* Apply the corrected vertical scrolling to the scrollView
*/
// Resize the scrollView to fill all empty space
let newScrollViewY = self.header.frame.origin.y + self.header.frame.height
scrollView.frame = CGRect(
x: 0,
y: newScrollViewY,
width: scrollView.frame.width,
height: scrollView.frame.height + (scrollView.frame.origin.y - newScrollViewY)
)
// Translate the scrollView's content view down to contrast scrolling
let scrollTransform = CATransform3DTranslate(CATransform3DIdentity, 0, (actualOffset), 0)
scrollView.subviews[0].layer.transform = scrollTransform
// Set bottom inset to show content hidden by translation
scrollView.contentInset = UIEdgeInsets(
top: 0,
left: 0,
bottom: actualOffset,
right: 0
)
self.currentOffset = actualOffset
}
If I haven't forgotten anything this should be enough to achieve the desired effect. Let me break it down:
I calculate the actualOffset binding it between 0 and self.MaxHeaderScroll which is just 67 (I think, it's calculated dynamically but this doesn't really matter)
If I see that the actualOffset hasn't changed since the last time this function was called I don't bother to aplly any changes. This avoids some useless calculations.
I apply the scrolling to the header by translating it up with a CATransform3DTranslate on just the y axis by negative actualOffset.
I call self.header.didScrollBy(actualOffset) so that the HeaderView can apply some visual changes internally. This doesn't concearn the question though.
I resize the scrollView so that it keeps 0 margin from top and bottom now that the HeaderView is higher up.
I translate down the scrollView's content by the same actualOffset amount to contrast the scrolling. This piece is essential to the correct visual effect that I want to achieve. If I didn't do this, the scrollView would still resize correctly but the content would start scrolling right away, which I don't want. It should only start scrolling once the HeaderView reaches it's minimum height.
I now set a bottom inset in the scrollView so that I am able to scroll it all the way to the end. Without this, the last part of the scrollView would be cut off since the scrollView itself would think it reached the end of it's content.
Lastly I store the actualOffset for later comparison
As I said, this works fine. The problem arises when I switch from a UIScrollView to a UITableView. I assumed it would work since UITableView inherits from UIScrollView.
The only piece of code that doesn't work is the number 6. I don't really know what is going wrong so I will just list everything I have found out and/or noticed. Hopefully someone will be able to help me out.
in the case of the UIScrollView, in point 6, the scrollView.subviews[0] refers to a view that holds all the content inside it. When I change to UITableView this subview seems to be of the type UITableViewWrapperView which I could not find any documentation about, nor does XCode recognize it as a valid class. This is already frustrating.
if in point 6 I also give some translation on the x axis (let's say of 50) I can see an initial very quick translation that is immediately brought back to 0. This only happens when the UITableView starts scrolling, it doesn't go on while scrolling.
I have tried changing the frame of the subview in point 6 to achieve the desired result. Although the scrolling is correct, the top cells start disappearing as I scroll the UITableView. I thin this is because I am using dequeueReusableCell(withIdentifier:for:) to instatiate the cells and the UITableView thinks that the top cells aren't visible when they actually are. I wasn't able to work around this problem.
I have tried setting the self.tableView.tableHeaderView to a UIView of the actualOffset height to contrast scrolling but this gave a weird effect where the cells would not scroll correctly and when the UITableView was brought back to the initial position, there would be a gap on top. No clue about this either.
I know there's a lot here so please don't hesitate asking for more details. Thank you in advance.
I made something like this recently, so heres how I achieved it:
Make a UIView with a height constraint constant and link this to your view/VC, have you UITableview constrained to the VC's view full screen behind the UIView.
Now set your UITableViews contentInset top to the starting height of your 'headerView' now, in the scrollViewDidScroll you adjust the constant until the height of the header is at its minimum.
Here is a demo
If you just run it, the blue area is your 'header' and the colored rows are just any cell. You can autolayout whatever you want in the blue area and it should auto size and everything

How to manually change UICollectionView height in Swift

I'm hiding rows that are completed in a collectionView.
I call cell.hidden = isCellHidden in cellForItemAtIndexPath when needed.
After I hide 10 rows there is plenty of empty space left and I'd like to trim down the size of the collectionView to only fit the rows that are not hidden.
The collectionView's design is kind of like a tableView.
I know with the tableView all I had to do to achieve this is set:
func section1VisibilityButton(sender: UIButton){
isCellHidden = !isCellHidden
self.tableView.reloadData()
self.tableView.contentSize.height = CGFloat(500)
}
with a collectionView when I try this it will resize it correctly but as soon as I try to scroll down it resizes itself back to the original height including the cells hidden (the cells layer is still hidden but there's tons of empty space bellow the last visible row as if they were visible)
For your issue, there are two options to change the frame of your collectionView/tableView.
If you are using autolayout, you need to create IBOutlet of bottom constraint or IBOutlet of constant height constraint of your tableView (anyone of these constraints, which you are using).
After reload tableView data you need to update constraint by calculating its height.
Suppose you are using constant height constraint and your calculated height is 150(e.g. 3 rows and 50 height of each row).
constraintTableViewHeight.constant = 150;//this will change height
self.view.layoutIfneed(); // this will apply updated constraints to whole view
If you are not using autolayout, you can manually change the height by changing tableView.frame property.

Resources