Nothing Occurring with ScrollToItem in UICollectionView - swift

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)

Related

Is there a way to give outer border in every section of uicollctionview?

Try to add outer border of every section in a collection view.
If i'm using cell.layer.border, it will also create an inner border. Is there a simple way to create outer border only for every section in collection view?
Try to created red border like image below
As Matt pointed out in the comments and the articles pointed out, you would need to make use of a DecorationView.
You can read up on this here
So to do this, you would have to follow these steps:
Create a custom UICollectionReusableView which would serve as your decoration view
Subclass UICollectionViewFlowLayout to create a custom layout
Override layoutAttributesForDecorationView and layoutAttributesForElements to figure out the frame of each section and place the decoration view in the section frame
Use the custom flow layout as the layout of your collection view
Here is that in code
Create the Decoration view, which is just a regular view with a border
class SectionBackgroundView : UICollectionReusableView {
static let DecorationViewKind = "SectionBackgroundIdentifier"
override init(frame: CGRect) {
super.init(frame: frame)
// Customize the settings to what you want
backgroundColor = .clear
layer.borderWidth = 5.0
layer.borderColor = UIColor.blue.cgColor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Create a custom flow layout
class BorderedFlowLayout: UICollectionViewFlowLayout {
override init() {
super.init()
// Register your decoration view for the layout
register(SectionBackgroundView.self,
forDecorationViewOfKind: SectionBackgroundView.DecorationViewKind)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutAttributesForDecorationView(ofKind elementKind: String,
at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
if elementKind == SectionBackgroundView.DecorationViewKind {
guard let collectionView = collectionView else { return nil }
// Initialize a UICollectionViewLayoutAttributes for a DecorationView
let decorationAttributes
= UICollectionViewLayoutAttributes(forDecorationViewOfKind: SectionBackgroundView.DecorationViewKind,
with:indexPath)
// Set it behind other views
decorationAttributes.zIndex = 2
let numberOfItemsInSection
= collectionView.numberOfItems(inSection: indexPath.section)
// Get the first and last item in the section
let firstItem = layoutAttributesForItem(at: IndexPath(item: 0, section: indexPath.section))
let lastItem = layoutAttributesForItem(at: IndexPath(item: (numberOfItemsInSection - 1),
section: indexPath.section))
// The difference between the maxY of the last item and
// the the minY of the first item is the height of the section
let height = lastItem!.frame.maxY - firstItem!.frame.minY
// Set the frame of the decoration view for the section
decorationAttributes.frame = CGRect(x: 0,
y: firstItem!.frame.minY,
width: collectionView.bounds.width,
height: height)
return decorationAttributes
}
return nil
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// Get all the UICollectionViewLayoutAttributes for the current view port
var attributes = super.layoutAttributesForElements(in: rect)
// Filter to get all the different sections
let sectionAttributes
= attributes?.filter { $0.indexPath.item == 0 } ?? []
// Loop through the different sections
for sectionAttribute in sectionAttributes {
// Create decoration attributes for the current section
if let decorationAttributes
= self.layoutAttributesForDecorationView(ofKind: SectionBackgroundView.DecorationViewKind,
at: sectionAttribute.indexPath) {
// Add the decoration attributes for a section if it is in the current viewport
if rect.intersects(decorationAttributes.frame) {
attributes?.append(decorationAttributes)
}
}
}
return attributes
}
}
Make use of the custom layout in your view controller
private func configureCollectionView() {
collectionView = UICollectionView(frame: CGRect.zero,
collectionViewLayout: createLayout())
collectionView.backgroundColor = .white
collectionView.register(UICollectionViewCell.self,
forCellWithReuseIdentifier: "cell")
// You can ignore the header and footer views as you probably already did this
collectionView.register(HeaderFooterView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: HeaderFooterView.identifier)
collectionView.register(HeaderFooterView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter,
withReuseIdentifier: HeaderFooterView.identifier)
collectionView.dataSource = self
collectionView.delegate = self
view.addSubview(collectionView)
}
private func createLayout() -> UICollectionViewFlowLayout {
let flowLayout = BorderedFlowLayout()
flowLayout.minimumLineSpacing = 10
flowLayout.minimumInteritemSpacing = 10
flowLayout.scrollDirection = .vertical
flowLayout.sectionInset = UIEdgeInsets(top: 10,
left: horizontalPadding,
bottom: 10,
right: horizontalPadding)
return flowLayout
}
Doing all of this should give you what you want
I have only posted the most important snippets. If for some reason you can't follow along, here is the full code to recreate the example

CollectionView Cell Changing Size when screen reloads

I am creating a screen where the user can search for films and the results load in a collection View, everything loads perfectly but in the simulator when I clicked "command, shift, A" to change to light mode to make sure the colours would adapt correctly the Cells randomly changed size to full screen instead of what I have set.
This also happens when I leave the application for the home page and then click back into the app. I am creating everything programmatically so would need answer this way please.
Below is the code from my custom cell:
import UIKit
class FavouritesCell: UICollectionViewCell {
static let identifier = "FavouritesCell"
let movieTitle: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.lineBreakMode = .byWordWrapping
label.textColor = .secondaryLabel
//label.text = "Title"
label.adjustsFontSizeToFitWidth = true
label.textAlignment = .center
label.font = label.font.withSize(12)
label.minimumScaleFactor = 0.75
return label
}()
let image : UIImageView = {
let image = UIImageView()
image.clipsToBounds = true
image.translatesAutoresizingMaskIntoConstraints = false
//image.backgroundColor = .yellow
image.layer.cornerRadius = 10.0
return image
}()
let cancelItem : UILabel = {
let label = UILabel()
label.layer.borderWidth = 2.0
label.layer.borderColor = UIColor.systemGray.cgColor
label.text = "X"
label.textAlignment = .center
label.layer.cornerRadius = 15
//label.isHidden = true
return label
}()
override init(frame: CGRect) {
super.init(frame: .zero)
contentView.addSubview(image)
contentView.addSubview(movieTitle)
contentView.addSubview(cancelItem)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
image.frame = CGRect(x: 0, y: 25, width: contentView.width, height: contentView.height - 30 )
movieTitle.frame = CGRect(x: 0, y: 0, width: contentView.width, height: 25)
cancelItem.frame = CGRect(x: image.right - 25, y: image.top , width: 25, height: 25)
}
}
Here is the code from the view controller in relation to the collectionView:
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 2
layout.minimumInteritemSpacing = 2
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(FavouritesCell.self, forCellWithReuseIdentifier: FavouritesCell.identifier)
collectionView.clipsToBounds = true
collectionView.backgroundColor = UIColor.systemBackground
return collectionView
}()
NSLayoutConstraint.activate([
searchText.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
searchText.heightAnchor.constraint(equalToConstant: 50),
searchText.leadingAnchor.constraint(equalTo: view.leadingAnchor),
searchText.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.topAnchor.constraint(equalTo: searchText.bottomAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (view.width / 3) - 4, height: (view.width / 2) - 2 )
}
image of CollectionView working normally
After the screen has been reloaded
Try to give your imageView a fixed height and fixed width and try it again, It will work

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

UICollectionView ScrollTo Crashing

I am attempting to have my UICollectionView in my custom UIView, be able to scroll to a page when a button is pressed in the main VC. Unfortunately everytime I call the method to do it in the MainVC, it crashes, # to scrollToItem .
here is my MainVC.
let mainView = MainViewsHome()
In the ViewWillAppear:
/** 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)
&& My custom UIView
class MainViewsHome: UIView, UIGestureRecognizerDelegate, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var cellId = "Cell"
var collectionView : UICollectionView!
override init(frame: CGRect) {
super.init(frame: CGRect(x:0, y:0, width:UIScreen.main.bounds.width, height: UIScreen.main.bounds.height / 1.3))
/**Creation of the View **/
let flowLayout : UICollectionViewFlowLayout = UICollectionViewFlowLayout()
flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
flowLayout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: CGRect(x:self.frame.width * 0,y:self.frame.height * 0,width:self.frame.width,height: self.frame.height), collectionViewLayout: flowLayout)
collectionView.register(uploadGenreSelectionCVC.self, forCellWithReuseIdentifier: cellId)
collectionView.isPagingEnabled = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = UIColor.purple
self.addSubview(collectionView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func moveToPage() {
print("we are moving to page")
self.collectionView.scrollToItem(at: 2 as IndexPath, at: UICollectionViewScrollPosition.right, animated: true)
}
&& Then in my MainVC I am calling: mainView.moveToPage() Where am I going wrong with my understanding?
Problem is with 2 as IndexPath, IndexPath is not Integer so you need to make the object of IndexPath using its init init(item:section:). and after that scroll collectionView to specific item this way.
func moveToPage() {
let indexPath = IndexPath(item: 2, section: 0) //Change section from 0 to other if you are having multiple sections
self.collectionView.scrollToItem(at: indexPath, at: .right, animated: true)
}
Note: CollectionView's items start at 0 index so if you want to scroll to 2nd item then make indexPath using IndexPath(item: 1, section: 0) because 2nd will scroll at 3rd cell of collectionView.

Swift CollectionView Vertical Paging

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