Swift CollectionView Vertical Paging - swift

I enabled paging on my collectionview and encountered the first issue of each swipe not stopping on a different cell, but page.
I then tried to enter code and handle the paging manually in ScrollViewWillEndDecelerating.
However my problem is the changing the collectionviews ContentOffset or scrolling to a Point both do not work unless called in LayoutSubiews. that is the only place where they effect the CollectionView
Each cell in my collection view is full screen. I handle the cell sizing in layout subviews.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let layout = customCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
let itemWidth = view.frame.width
let itemHeight = view.frame.height
layout.itemSize = CGSize(width: itemWidth, height: itemHeight)
layout.invalidateLayout()
}
let ip = IndexPath(row: 2, section: 0)
view.layoutIfNeeded()
customCollectionView.scrollToItem(at: ip, at: UICollectionViewScrollPosition.centeredVertically, animated: true)
let point = CGPoint(x: 50, y: 600)
customCollectionView.setContentOffset(point, animated: true)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let pageHeight = customCollectionView.bounds.size.height
let videoLength = CGFloat(videoArray.count)
let minSpace:CGFloat = 10
var cellToSwipe = (scrollView.contentOffset.y) / (pageHeight + minSpace) + 0.5
if cellToSwipe < 0 {
cellToSwipe = 0
}
else if (cellToSwipe >= videoLength){
cellToSwipe = videoLength - 1
}
let p = Int(cellToSwipe)
let roundedIP = round(Double(Int(cellToSwipe)))
let ip = IndexPath(row: Int(roundedIP), section: 0)
let ip = IndexPath(row: 2, section: 0)
view.layoutIfNeeded()
customCollectionView.scrollToItem(at: ip, at: UICollectionViewScrollPosition.centeredVertically, animated: true)
}

First off, I would recommend making your view controller class conform to UICollectionViewDelegateFlowLayout and use the following delegate method to size your UICollectionViewCells
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = CGSize(width: view.frame.width, height: view.frame.height)
return size
}
Then in your viewDidLoad call
customCollectionView.isPagingEnabled = true

Related

Swift CollectionView slider with preview of next page

I have slider which display image on each cell. What I want to do, is display 10% of next image on current page and slider it to next page. Each image should be centered in own page (3 in total).
extension OnboardingViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width - 200, height: view.frame.height)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let cellWidth : CGFloat = view.frame.width - 200
let numberOfCells = floor(self.view.frame.size.width / cellWidth)
let edgeInsets = (self.view.frame.size.width - (numberOfCells * cellWidth)) / (numberOfCells + 1)
return UIEdgeInsets(top: 15, left: edgeInsets, bottom: 0, right: edgeInsets)
}
}
What i did is... i put left edge inset. Made my cell smaller, like 0.74 of initial size, i add 2 methods to control my slides when i drag slide...
then i add animation to my custom cell with flag previewed. So.. my initial slide always previewed and has size 1.0, all other has 0.9 size, when i drag image i change previewed state to false for all slides and do true only for presented.
0.74 is size of my cell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let cellWidth : CGFloat = view.frame.width * 0.74
let numberOfCells = floor(self.view.frame.size.width / cellWidth)
let edgeInsets = (self.view.frame.size.width - (numberOfCells * cellWidth)) / (numberOfCells + 1)
return UIEdgeInsets(top: 0, left: edgeInsets, bottom: 0, right: edgeInsets)
}
i found custom size to my case
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width * 0.74, height: view.frame.height)
}
And i wrote 2 methods to control my slides
func scrollViewWillEndDragging(_ scrollView: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let x = targetContentOffset.pointee.x
pageControl.currentPage = Int(x / view.frame.width * 0.74)
let last = viewModel.getNumberOfItems() - 1
if pageControl.currentPage == last {
btnStart.isHidden = false
imbBtnStartBg.isHidden = false
} else {
btnStart.isHidden = true
imbBtnStartBg.isHidden = true
}
}
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
let pageWidth = view.frame.width * 0.74
let page = floor((collectionView.contentOffset.x - pageWidth / 2) / pageWidth) + 1
let xOffset: CGFloat = page * pageWidth
collectionView.setContentOffset(CGPoint(x: xOffset, y: 0.0), animated: true)
pageControl.currentPage = Int(page)
if Int(page) == 2{
btnStart.isHidden = false
imbBtnStartBg.isHidden = false
} else {
btnStart.isHidden = true
imbBtnStartBg.isHidden = true
}
for i in 0..<viewModel.onBoardingModels.count{
viewModel.onBoardingModels[i].previewed = false
}
viewModel.onBoardingModels[Int(page)].previewed = true
collectionView.reloadData()
}
Also i add animation to my cell
UIView.animate(withDuration: 0.5, animations: {
self.imgv.transform = model.previewed ? CGAffineTransform(scaleX: 1, y: 1) : CGAffineTransform(scaleX: 0.9, y: 0.9)
})

Collectionview the end Draging Position dont stop on each cell

could somebody help me?
I have Collection view with insets, Scroll Direction Horizontal, Paging enables and no space between cells. And the issue is that the paging doesn't stop on each cell.
I tried all possible solution which I can find on StackOverflow and the World Wide Web but nothing works. I use Xcode 10 and Swift5 and this is for an iPhone app. I don't add my collectionviewcell code because it have only the IBoutlet for the Label.
class ViewController: UIViewController, UICollectionViewDataSource,
UICollectionViewDelegate {
#IBOutlet weak var collectionView: UICollectionView!
private let item = ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday", "Sunday"]
private let cellScaling: CGFloat = 0.33
private var cellWidth: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
UpdateUI()
}
func UpdateUI(){
self.collectionView.backgroundColor = UIColor.white
let screenSize = UIScreen.main.bounds.size
let cellWidth = floor(screenSize.width * cellScaling)
let insetX = (view.bounds.width - cellWidth) / 2
let layout = collectionView!.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = CGSize(width: cellWidth, height: collectionView.bounds.height)
collectionView.contentInset = UIEdgeInsets(top: 0, left: insetX, bottom: 0, right: insetX)
// layout.minimumLineSpacing = 0
self.cellWidth = cellWidth
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.item.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
cell.label.text = self.item[indexPath.row]
cell.backgroundColor = UIColor.cyan
return cell
}
private var startingScrollingOffset = CGPoint.zero
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
startingScrollingOffset = scrollView.contentOffset
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let page: CGFloat
let offset = scrollView.contentOffset.x + scrollView.contentInset.left
let proposedPage = offset / max(1, cellWidth)
let snapPoint: CGFloat = 0.1
let snapDelta: CGFloat = offset > startingScrollingOffset.x ? (1-snapPoint) : snapPoint
if floor(proposedPage + snapDelta) == floor(proposedPage){
page = floor(proposedPage)
} else {
page = floor(proposedPage + 1)
}
targetContentOffset.pointee = CGPoint(x: cellWidth * page, y: targetContentOffset.pointee.y)
}
}
enter image description here
And please Notice that I tried all other solution what I could find. Before you vote to delete this Question. the photo shows what it should look like on every cell.
I had to do a similar collection view a few months ago,
This is the code that I use:
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let layout = theNameOfYourCollectionView.collectionViewLayout as! UICollectionViewFlowLayout
let cellWidthIncludingSpacing = layout.itemSize.width + layout.minimumLineSpacing // Calculate cell size
let offset = scrollView.contentOffset.x
let index = (offset + scrollView.contentInset.left) / cellWidthIncludingSpacing // Calculate the cell need to be center
if velocity.x > 0 { // Scroll to -->
targetContentOffset.pointee = CGPoint(x: ceil(index) * cellWidthIncludingSpacing - scrollView.contentInset.right, y: -scrollView.contentInset.top)
} else if velocity.x < 0 { // Scroll to <---
targetContentOffset.pointee = CGPoint(x: floor(index) * cellWidthIncludingSpacing - scrollView.contentInset.left, y: -scrollView.contentInset.top)
} else if velocity.x == 0 { // No dragging
targetContentOffset.pointee = CGPoint(x: round(index) * cellWidthIncludingSpacing - scrollView.contentInset.left, y: -scrollView.contentInset.top)
}
}
This code calculates the size of your cell, how many cells have already been shown and once the scroll is finished, adjust it to leave the cell centered.
Make sure you have the pagingEnabled of your collectionView in false if you want to use this code.
I hope I help you!

NSCollectionView viewForSupplementaryElementOfKind not being called

In my NSCollectionViewItem I set up the code below.
func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
let view = collectionView.makeSupplementaryView(ofKind: .sectionHeader, withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "sectionHeader"), for: indexPath)
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.red.cgColor
return view
}
func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> NSSize {
return NSSize(width: self.frame.size.width, height: 60)
}
However, when I place a print() in the viewForSupplementaryElementOfKind function it never is executed. Also, no red header appears.
This is how I set up the NSCollectionView
public let collectionView: NSCollectionView = {
let v = NSCollectionView(frame: NSRect.zero)
v.wantsLayer = true
v.layer!.backgroundColor = NSColor.clear.cgColor
v.isSelectable = true
v.backgroundColors = [NSColor.clear]
v.allowsEmptySelection = false
v.collectionViewLayout = {
let l = NSCollectionViewFlowLayout()
l.minimumInteritemSpacing = 1
l.minimumLineSpacing = 1
l.scrollDirection = NSCollectionView.ScrollDirection.vertical
l.sectionInset = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
return l
}()
return v
}()
What am I doing wrong?
The supplemental views (header and footer) are not collection view items. You need to implement those two methods on the NSCollectionViewDelgateFlowLayout object, not on your NSCollectionViewItem class.
See https://developer.apple.com/documentation/appkit/nscollectionviewdelegateflowlayout
You can also set a default for the entire collection view:
l.headerReferenceSize = NSSize(width: 0, height: 50)
l.footerReferenceSize = NSSize(width: 0, height: 50)
When using NSCollectionViewFlowLayout, the default size of the header and footer is NSSize.zero, so the method to make the supplemental view is not called unless one of these techniques is used.

Swift 4 - Multiple inheritance from classes 'NSObject' and 'UICollectionViewFlowLayout'

I am trying to programatically size the collection cell to be the width of the frame, however, the cell size doesn't change when I run the app. Is this the right function to call for Swift 4 and Xcode 9?
import UIKit
class SettingsLauncher: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewFlowLayout {
let blackView = UIView()
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.white
return cv
}()
let cellID = "cell"
#objc func showMenu() {
// Show menu
if let window = UIApplication.shared.keyWindow { // Get the size of the entire window
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
let height: CGFloat = 200
let y = window.frame.height - height // The y value to appear at the bottom of the screen
collectionView.frame = CGRect(x: 0, y: window.frame.height, width: window.frame.width, height: height)
// Add gesture recognizer on black view to dismiss menu
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleDismiss)))
window.addSubview(blackView)
window.addSubview(collectionView)
blackView.frame = window.frame
blackView.alpha = 0
// Slow the animation down towards the end (curveEasOut)
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
// Animate black view
self.blackView.alpha = 1
// Animate collection view
self.collectionView.frame = CGRect(x: 0, y: y, width: self.collectionView.frame.width, height: height)
}, completion: nil)
}
}
#objc func handleDismiss() {
// Dimisses menu view
UIView.animate(withDuration: 0.5) {
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow {
self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 6
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 50)
}
override init() {
super.init()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(MenuCell.self, forCellWithReuseIdentifier: cellID)
}
}
Edit:
This table view is being animated from the bottom of the page and presents a collection view, which is being called from the view controller that is using the pop up menu.
I now get the error:
Multiple inheritance from classes 'NSObject' and 'UICollectionViewFlowLayout'
UICollectionViewFlowLayout is not a protocol, it's a class. You need to use UICollectionViewDelegateFlowLayout instead of UICollectionViewFlowLayout.
Change
class SettingsLauncher: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewFlowLayout
to
class SettingsLauncher: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout

Nothing Occurring with ScrollToItem in UICollectionView

I have two collection views within a ViewController. One Collection view represents the tab bar at the top, the other represents pages on the bottom half of the screen. I have the second UICollectionView (on the bottom half) within a custom UIView (loading from another class). My objective is to have when I click on the tab bar, to move the bottom collectionView accordingly, with regards to the scrollToIndex. My issue is that when I click on the tab bar at the top, and call the function that is in my custom UIView, nothing occurs. Do I need to share information through a delegate/protocol? What am I doing incorrectly?
In my MainVC I have:
/** Setting up the top header **/
let topBar = UIView()
/** Setting up the connection to the Bottom Collection View **/
let mainView = MainViewsHome()
override func viewWillAppear(_ animated: Bool) {
self.view.layoutIfNeeded()
/**Setting up the header that the buttons lay on **/
topBar.frame = CGRect(x:self.view.frame.width * 0, y:self.view.frame.height * 0, width:self.view.frame.width,height:self.view.frame.height / 6.2)
topBar.backgroundColor = UIColor.red
self.view.addSubview(topBar)
/** Setting up the buttons in the header**/
setUpHeaderBarButtons()
/** Setting up the bottom half **/
mainView.frame = CGRect(x:self.view.frame.width * 0, y:self.view.frame.height / 6.2, width:self.view.frame.width,height:self.view.frame.height / 1.1925)
mainView.backgroundColor = UIColor.clear
self.view.addSubview(mainView)
mainView.delegate = self
}
& Then setting up the collectionView for the MainVC (the bar buttons)
func setUpHeaderBarButtons() {
let flowLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: CGRect(x: topBar.frame.width * 0, y:topBar.frame.height / 1.75, width: topBar.frame.width, height: topBar.frame.height / 3.6), collectionViewLayout: flowLayout)
collectionView.register(SelectionCVC.self, forCellWithReuseIdentifier: cellId)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = UIColor.clear
topBar.addSubview(collectionView)
}
&& Then the didSelect for the MainVC (The bar buttons)
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("selecting cell")
mainView.moveToPage()
}
&& Then my custom UIVIew with the bottom collection view that I wish to trigger a scrollToIndex
class MainViewsHome: UIView, UIGestureRecognizerDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var cellId = "Cell"
var collectionView2 : UICollectionView!
override init(frame: CGRect) {
super.init(frame: CGRect(x:0, y:0, width:UIScreen.main.bounds.width, height: UIScreen.main.bounds.height / 1.27))
/**Creation of the View **/
let flowLayout2 : UICollectionViewFlowLayout = UICollectionViewFlowLayout()
flowLayout2.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
flowLayout2.scrollDirection = .horizontal
let collectionView2 = UICollectionView(frame: CGRect(x:self.frame.width * 0,y:self.frame.height * 0,width:self.frame.width,height: self.frame.height), collectionViewLayout: flowLayout2)
collectionView2.register(SelectionCVC.self, forCellWithReuseIdentifier: cellId)
collectionView2.isPagingEnabled = true
collectionView2.delegate = self
collectionView2.dataSource = self
collectionView2.backgroundColor = UIColor.purple
self.addSubview(collectionView2)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func moveToPage() {
// print("we are moving to page")
let indexPath = IndexPath(item: 2, section: 0) //Change section from 0 to other if you are having multiple sections
self.collectionView2?.scrollToItem(at: indexPath, at: [], animated: true)
}
}
where am i going wrong?
In the init of MainViewsHome instead of initializing instance property collectionView2 you are initializing completely new collectionView so you are not having reference in collectionView2.
It should be
self.collectionView2 = UICollectionView(frame: CGRect(x:self.frame.width * 0,y:self.frame.height * 0,width:self.frame.width,height: self.frame.height), collectionViewLayout: flowLayout2)
Instead of
let collectionView2 = UICollectionView(frame: CGRect(x:self.frame.width * 0,y:self.frame.height * 0,width:self.frame.width,height: self.frame.height), collectionViewLayout: flowLayout2)