Is there a way to allow paging in a UIScrollView but restrict the paging to one direction.
For example: Allowing the user to page background (to the left) but not forward. Use case being for some type of onboarding. I know I can add buttons that move forward, have them enabled or disabled, and remove swiping but I rather not.
I don't know if I have well understand what you want but what about using a UICollectionView:
lazy var onBoardingCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
let cv = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
cv.showsHorizontalScrollIndicator = false
cv.showsVerticalScrollIndicator = false
cv.isPagingEnabled = true
return cv
}()
You add you custom cell with the size of the screen as well by the method in UICollectionViewDelegateFlowLayout:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: SCREENW, height: SCREENH)
}
if you don't want to go in a specific direction you can try get the current cell displaying and removing the previous one of the dataSource and collection with this method.
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let currentPage = Int(scrollView.contentOffset.x / scrollView.frame.width)
print("current cell: ", currentPage)
}
Related
I have a table view and collection view to display image. Table view for vertical scroll and collection view for horizontal scroll. The image is displayed as full screen for the device. But when I scroll vertically half of previous cell is displayed and half of next cell is displayed. I used Table view constant is 0 to superview on all 4 constraints.
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return myTableView.frame.size.height;
}
To return the height of the cell but even this is causing same issue.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.myTableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
return cell
}
This table view cell contains collection view
class TableViewCell: UITableViewCell,UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout{
func setUpCollectionView(){
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero,collectionViewLayout: layout)
collectionView?.register(VideoCollectionViewCell.self, forCellWithReuseIdentifier: VideoCollectionViewCell.identifier)
collectionView?.isPagingEnabled = true
collectionView?.dataSource = self
collectionView?.delegate = self
self.contentView.addSubview(collectionView!)
collectionView?.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView!.topAnchor.constraint(equalTo: contentView.topAnchor),
collectionView!.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
collectionView!.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
collectionView!.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.frame.width, height: self.frame.height)
}
}
This is my implementation, I don't want images to be displayed on half/half portion. I am trying to show it on full screen. Can anyone tell me why this is happening?
you need only UICollectionView display list images and set property scroll horizontal in UICollectionViewFlowLayout
myCollectionView.dataSource = self
myCollectionView.delegate = self
myCollectionView.contentMode = .scaleAspectFill
// scroll the images page by page, set property isPagingEnabled is true
myCollectionView.isPagingEnabled = true
let layout = UICollectionViewFlowLayout()
// set sroll horizontal here
layout.scrollDirection = .horizontal
myCollectionView.collectionViewLayout = layout
I am having 2 collection views in my view controller I want to have collectionView sizeForItemAt method just for one of them and not change the other one.
I have to set other delegates for both of them.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if (collectionView == collectionView1) {
return CGSize(width: 158, height: 90)
} else if (collectionView == collectionView2) {
let collectionWidth = (collectionView.bounds.size.width - 20) / 3
return CGSize(width: collectionWidth, height: collectionWidth)
}
}
I don't want to return anything for collectionview2 because its size is dynamic and based on device size. which is done by a flowLayout.
Is there a way just to implement this method for the first one?
It seems that all the cells of collectionView1 has the same size. If so, use estimatedItemSize. It is an instance property of UICollectionViewFlowLayout.
estimatedItemSize
The estimated size of cells in the collection view. developer.apple.com
let layoutOne = UICollectionViewFlowLayout()
layoutOne.estimatedItemSize = CGSize(width: 158, height: 90)
let collectionViewOne = CollectionViewOne(frame: .zero, collectionViewLayout: layoutOne)
// CollectionView2 must have its own layout
let layout = UICollectionViewFlowLayout()
let collectionViewTwo = CollectionView(frame: .zero, collectionViewLayout: layout)
If you do so, remove collectionView(_:layout:sizeForItemAt:).
I read
How to vertically center UICollectionView content
But when I used the codes here
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let navBarHeight = self.navigationController?.navigationBar.frame.height
let collectionViewHeight = (self.collectionView?.frame.height)! - navBarHeight!
let itemsHeight = self.collectionView?.contentSize.height
let topInset = ( collectionViewHeight - itemsHeight! ) / 4
return UIEdgeInsetsMake(topInset ,0, 0 , 0)
}
But when scrolling collection view this will show incorrect form
so here is my codes because my collection view cells are square and equal to screen width
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
showImagesCV.contentInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
if let flowLayout = showImagesCV.collectionViewLayout as? UICollectionViewFlowLayout{
cellWidth = UIScreen.main.bounds.width
cellHeight = cellWidth //yourCellHeight = cellWidth if u want square cell
flowLayout.minimumLineSpacing = spacing
flowLayout.minimumInteritemSpacing = spacing
UIView.performWithoutAnimation {
}
}
}
}
extension showImagesViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: cellWidth, height: cellHeight)
}
}
I want the spacing between cells be 0 and each cell that is showing (current page (because the collection view is in paging mode)) be the center of the screen
Regarding the SO Question you mentioned and using, that is to centre the whole section not individual cells. Some of your code might be invalid(needs to be redone).
First and most important, set the constraints of your collectionView for full screen. Remember to take safeAre/TopGuide into account, this will make sure that collection view is below the navigation bar if there is one.
This will make sure that your collectionView is up to date
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
myCollectionView.collectionViewLayout.invalidateLayout()
}
Dump/comment below code and set insets to (0,0,0,0) in inspector of collectionView in story board. In same inspector change Min Spacing to 0 for cells and for line, both.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let navBarHeight = self.navigationController?.navigationBar.frame.height
let collectionViewHeight = (self.collectionView?.frame.height)! - navBarHeight!
let itemsHeight = self.collectionView?.contentSize.height
let topInset = ( collectionViewHeight - itemsHeight! ) / 4
return UIEdgeInsetsMake(topInset ,0, 0 , 0)
}
Also remove/comment below code
showImagesCV.contentInset = UIEdgeInsets(top: spacing, left: spacing, bottom: spacing, right: spacing)
if let flowLayout = showImagesCV.collectionViewLayout as? UICollectionViewFlowLayout{
cellWidth = UIScreen.main.bounds.width
cellHeight = cellWidth //yourCellHeight = cellWidth if u want square cell
flowLayout.minimumLineSpacing = spacing
flowLayout.minimumInteritemSpacing = spacing
UIView.performWithoutAnimation {
}
}
Now change your sizeForItemAt like this.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return collectionView.frame.size
}
UICollectionViewCell
Now in your collectionView cell, make sure that your image view is 1:1 and in centre. Also handle constraints of your other UI components.
PS : This is easy approach, other would be to write custom flowLayout
I have a collectionView setup as following, but in sizeForItemAt the collectionView!.frame.size is not the same as the rendered result.
Note: I am only using Constraints for the layout of the collectionView.
Any idea?
public override init(frame: CGRect){
super.init(frame: frame)
self.translatesAutoresizingMaskIntoConstraints = false
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.scrollDirection = .vertical
self.collectionView = UICollectionView(frame: self.frame, collectionViewLayout: layout)
self.addSubview(self.collectionView!)
self.collectionView?.translatesAutoresizingMaskIntoConstraints = false
self.collectionView!.delegate = self
self.collectionView!.dataSource = self
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellIdentifier")
}
public func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
collectionView!.frame.size // Tot the size of the rendered collectionView, and therefore unable to give the cell the right size???
}
This is happening because the cell in your UICollectionView are loaded before the layout of the view controller is finished.
When using constraints and collection views, I noticed that it is unreliable to use the collection view size before the viewDidAppear(:) call.
If you want to do a grid layout relative to the screen size, you can use UIScreen.main.bounds.size in the sizeForItemAt method.
Here, your collection view seems to be a subview of a UIView subclass. So if you can't or don't want to use the screen size, you can also reload the collection view after its superview bounds changed:
override var bounds: CGRect {
didSet {
if oldValue != bounds {
self.collectionView?.reloadData()
}
}
}
I have a collection view with 6 elements that should be laid out with 3 rows and 2 columns. I use the following code to achieve this:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let totalWidth = collectionView.bounds.size.width - 12
let totalHeight = collectionView.bounds.size.height - 18
let heightOfView = (totalHeight / 3)
let dimensions = CGFloat(Int(totalWidth))
return CGSize(width: dimensions / 2, height: heightOfView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 6
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(0, 5, 0, 5)
}
In viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 5
flowLayout.minimumInteritemSpacing = 5
This produces a collection view that looks like this which is perfect:
This is the desired output
However when I situationally hide a view below the collection view before the view is loaded and adjust the size of it(making it 50 pts taller in height), then the output changes to the following (see image) with cells 4 & 5 off the screen unless scrolled to, only 2 rows instead of 3 and a large gap between rows:
Collection view appearance when adjusted to account for hidden view
To situationally hide the view in viewWillAppear I added the following:
override func viewWillAppear(_ animated: Bool) {
if testVariable == true {
bottomView.isHidden = true
// make bottomView height = 0
bottomViewHeight.constant = 0
// make collectionView space to the bottom of the view controller 0
collectionToBase.constant = 0
view.layoutIfNeeded()
}
}
This code tests if testVariable is true, and if it is hides the bottomView and makes the collectionView 50 pts larger to fill the space. I added this code in viewWillAppear in the hope that collectionViewLayout would account for the extra 50 pts of height and adjust according, but it doesnt.
You need to invalidate the layout. It does not do so on its own. Call invalidateLayout() on the flow layout after your call to layoutIfNeeded().
flowLayout.invalidateLayout()