How to remove linebreak from UICollectionViewFlowLayout - ios

I am trying to remove line breaks for the items within my section using the UICollectionViewFlowLayout .
At the moment I have
|header |
|0-0 0-1 0-2 0-3|
|0-4 0-5 |
|header |
|1-0 1-1 1-2 0-3|
|1-4 1-5 |
and I need :
|header |
|0-0 0-1 0-2 0-3| 0-4 0-5
|header |
|1-0 1-1 1-2 1-3| 1-4 1-5
so users can horizontally scroll . On iOS i resolve this by creating two nested collectionViews but on tvOS I am unable to replicate the solution because i am unable to focus on the inner cells .
after several tries I override the preferredFocusedView variable on the tableCell where the nested UICollectionView is :
override var preferredFocusedView: UIView? {
return moviesCollection
}
This behavior allows me to swipe horizontally between the inner elements of the collection but i cannot swipe vertically to change between table cells.
I am kind of lost ,Any kind of help would be greatly appreciated.
Thanks .

This can be solved very quickly with a custom UICollectionViewLayout
Subclass UICollectionViewLayout & override the following properties and methods
var collectionViewContentSize: CGSize
func prepare()
func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
Make sure to return the correct frame for each cell
Here is my working solution that also conveniently provides some properties of UICollectionViewFlowLayout (itemSize, minimumInteritemSpacing, minimumLineSpacing).
You might not need to change anything and just add it to your project.
https://gist.github.com/kflip/52ec15ddb07aa830d583856909fbefd4
Feel free to add scrollDirection support ... its scrolling in both directions already, but you might want to change if one section should be displayed as column instead of a row...
You could also add FlowLayout-like protocol functionalities if you need more customisations based on IndexPaths (e.g. CellSize)

Updated
Here's a GIF showing the end result:
Original
I've just created a test ViewController and this seems to work alright.
Basically, what I have there is a UICollectionView with a vertical flow layout. Each cell of that collectionView has it's own UICollectionView set with an horizontal flow layout.
The only incovenient for the "out of the behavior" is that the horizontal flow layour collection view does not "remember" the selected item when coming back to it but that can be solved with a little bit of playing around.
Hope it helps. Here's my code for it:
class ViewController: UIViewController, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [collectionView]
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: "ParentTestCell", for: indexPath)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
return collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "TestHeader", for: indexPath)
}
}
// Parent cell - included in the vertical collection view & includes the horizontal collection view
class ParentTestCell: UICollectionViewCell, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override var preferredFocusEnvironments: [UIFocusEnvironment] {
return [collectionView]
}
// MARK: - UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: "TestCell", for: indexPath)
}
}
// Child cell - included in the horizontal collection view
class TestCell: UICollectionViewCell {
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
coordinator.addCoordinatedAnimations({
if self.isFocused {
self.backgroundColor = .red
}
else {
self.backgroundColor = .blue
}
}, completion: nil)
}
}
And here's the storyboard structre:

Just a suggestion if it can help you.
Add tableView
Create Custom TableVeiwCell which contains collection view inside the cell with horizontal scrolling.
for each section you have, will become number rows for tableview
for items in row becomes number of items for the respective collection view.

A UICollectionView lay out its cells in a very specific way when using UICollectionViewFlowLayout, I'm not sure it's possible to make that kind of behaviour with a collectionview, whitout making your own UICollectionViewLayout. See this guide on how to make a UIColloctionView with a custom layout.
I would suggest trying to solve the problem, by using nested UITableViews. Make a vertical scrolling tableview, where every cell in the tableview has it's own horizontal scrolling tableview. The cells in the horizontal tableview, will be the actual cells that the user can select. See this guide on how to do this.
This should work in tvOS as well, since the focus engine should look through the cells subviews, and find the focusable ones. I hope it helps :)

Related

Can I add a collection view to a collection view section header?

So I'm trying to figure out how to add two collection views on the name view controller. Right now the current collection view I have is a vertical scrolling collection view that displays users posts on the feed. I would like to add a "people to follow" section that scrolls horizontally on the top. Please note I would like the horizontal collection view to scroll down with the whole view.
Something that will look like this
I thought about adding a section header, then trying to add a collection view in that but I'm not sure if that is an illegal configuration.
I'm also not sure if I need to add 2 sections in the number of sections line.
Here is the collection view code currently.
#IBOutlet weak var collectionView: UICollectionView!
// MARK: - UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if posts.count > 4 {
if indexPath.item == posts.count - 1 {
fetchPosts()
}
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if viewSinglePost {
return 1
} else {
if posts.count == 0 {
self.collectionView.setEmptyMessage("You haven't followed anyone yet.")
} else {
self.collectionView.restore()
}
return posts.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PostsCell", for: indexPath) as! FollowingCell
cell.delegate = self
if viewSinglePost {
if let post = self.post {
cell.post = post
}
} else {
cell.post = posts[indexPath.item]
}
handleUsernameLabelTapped(forCell: cell)
handleMentionTapped(forCell: cell)
handleHashtagTapped(forCell: cell)
return cell
}
Right now its just a basic feed that will fetch posts users upload. I thought it would be great user experience to include a people to follow section for new uses. What is the best way I should go about this?
Seems like you want to add two collection views, one is for upper that is horizontal and the lower is vertical
Add two collection views in the storyboard, give the first one a
static height, take outlet from the storyboard and give outlet name something like UpperCollectionView and LowerCollectionView
set delegate and data source to the collectionViews
upperCollectionView.delegate = self
upperCollectionView.dataSource = self
lowerCollectionView.delegate = self
lowerCollectionView.dataSource = self
in case of all delegate methods , implement the collectionViews using if - else method
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == upperCollectionView {
//do code for upperCollectionView
} else {
//do code for lowerCollectionView
}
}
if look like complex, then I will suggest you to split, the upper part in the parent viewController and lower part in a container view.
If you want to scroll up the whole thing like a tableView scrolling, add all of this on a UIScrollView , this will handle the scrolling.
if you ask me i would suggest a table view with 2 cells, fist cell is for collection view and second cell is your normal cell. in that way you can scroll in both directions. But if you want to use collectionview only then you need to add tag for collection view and configure it according to your tag i.e
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView.tag == 1 {
// your horizantal collection view
} else {
//vertical one
}
You could define the first index as another collection view.

How to make pagination with UITableView along with UISegmentedControll

My view controller has a UISegmentedControll with 6 buttons/segment. It also has a UITableView under that Segmented Controll. If we switch segment by click then table view is reloading with different data. Now we are trying to implement a way where if user swipe the table view either in left-to-right or right-to-left, a pagination like behaviour will be appeared in the view controller. Means the table view will horizontally scrolled along with segmented controll. How can we implement this feature in my app in best approach ? My table has three different custom cell, one of which also has a UICollectionView inside the cell. Pls help me out. Thanks.
The idea I would suggest is to use tableView inside collectionView. Steps to follow-:
1) Add segmentedControl and collectionView on your controller.
2) Connect dataSource and delegate by control dragging from collectionView to controller yello icon.
3) Adjust your cell height.
4) Select collectionView, and go to attribute inspector. Look for-: 1) scroll direction and make horizontal. 2) Check Paging Enabled parameter.
5) Apply Constraints on views.
6) Give collectionView cell identifier.
7) Give your cell a custom collectionView cell class.
8) Connect all Outlets to respected views.
Controller class -:
import UIKit
class ViewController: UIViewController {
// OUTLETS
#IBOutlet weak var controls: UISegmentedControl!
#IBOutlet weak var horizontalCollectionView: UICollectionView!
// ARRAy OF TYPE UICOLOR
var collectionViewColors = [UIColor.gray,UIColor.green,UIColor.red,UIColor.yellow,UIColor.blue,UIColor.brown]
// ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
}
//didReceiveMemoryWarning
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Function to calculate cell index on scroll end.
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>){
let pagingIndex = targetContentOffset.pointee.x/self.view.frame.width
// Set segmented controll current index
controls.selectedSegmentIndex = Int(pagingIndex)
}
}
// Collection view methods
extension ViewController : UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
// number of section
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
// number of items in section
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return collectionViewColors.count
}
// deque cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collection", for: indexPath) as! HorizontalCell
cell.horizonatlColorsView.backgroundColor = collectionViewColors[indexPath.item]
return cell
}
// set minimum line spacing to zero.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
Custom cell class-:
import UIKit
class HorizontalCell: UICollectionViewCell {
// UIView outlet
#IBOutlet weak var horizonatlColorsView: UIView!
}
After setting up everything , scroll horizontally you will get the output you want.Also you can add tableView inside collectionView now.
Output-:

Collection View Cells not appearing in Collection View?

I created a Collection View using purely the storyboard interface builder. This is what the storyboard looks like:
My collection view's properties are default as well. I haven't written anything into my ViewController.swift yet.
For some reason, when I run on my phone / emulator, none of the buttons are showing.
UICollectionView does not support static cells like UITableView. You will have to set its dataSource,delegate and configure your cells in code.
Just configure the collectionView properly see below code and image:
Implement the delegate methods of collectionView:
class yourClassController: UICollectionViewDataSource, UICollectionViewDelegate {
override func numberOfSectionsInCollectionView(collectionView:
UICollectionView!) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView!,
numberOfItemsInSection section: Int) -> Int {
return yourArray.count
}
override func collectionView(collectionView: UICollectionView!,
cellForItemAtIndexPath indexPath: NSIndexPath!) ->
UICollectionViewCell! {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as CollectionViewCell
// Configure the cell
cell.backgroundColor = UIColor.blackColor()
cell.textLabel?.text = "\(indexPath.section):\(indexPath.row)"
cell.imageView?.image = UIImage(named: "circle")
return cell
}
Then from your storyboard set the delegate and datasource by drag and drop see image:
Note: collectionView appears when you do complete above formality with its relevant class.

UICollectionView cannot display customized cell content

I tried to create a dummy photo app with UICollectionView. My Xcode version is 7.2 and Swift version 2.1.1. I use Storyboard to build the UI part by following the tutorial from http://www.raywenderlich.com/78550/beginning-ios-collection-views-swift-part-1
In Swift 2.0, the UICollectionViewDataSource is inherited from UICollectionViewController, we don't need to explicitly declare those protocols. I implemented the required override methods for DataSource, and also register the customized cell in viewDidLoad() in the UICollectionViewController. I put a test Label in my cell to check whether it works or not. Unfortunately, the label never appear after I launch the app. I attach some of my code below as reference:
UICollectionViewController
class VacationsCollectionViewController: UICollectionViewController {
private let reuseIdentifier = "VacationCell"
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.registerClass(VacationsCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
// MARK: UICollectionViewDataSource
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return 1
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! VacationsCollectionViewCell
// Configure the cell
cell.backgroundColor = UIColor.blueColor()
cell.CellText.text = "Show me the money"
return cell
}
}
UICollectionViewCell
class VacationsCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var CellText: UILabel!
}
Any idea why the label never show up in my dummy app?
It looks like you have everything you need. The issue is probably with your storyboard. Just to be safe, add constraints to the label so it's definitely going to be displayed properly. Then, add an extension to your collection view controller and be sure the size allows the label to be displayed as well. This, at least, worked for me and I was in your exact situation.
extension OfficesCollectionViewController: UICollectionViewDelegateFlowLayout{
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSize(width: 200, height: 200)
}
}

Is completely static UICollectionView possible?

At UITableView, completely static tableView config is possible. You can disconnect UITableView's datasource and put each cell on storyboard(or xib) by using IB.
I tried same thing with UICollectionView. disconnect UICollectionView's datasource. Put each cell on UICollectionView on storyboard. I built it without any errors. But it didin't work. cells were not displayed at all.
Is UICollectionView without datasource possible?
No.
Creating a static UICollectionViewController is not allowed. You must have a data source delegate.
I also want to point out that there is not a static UITableView, but a static UITableViewController. It's a difference.
You can easily create a static UICollectionViewController.
Just create every cell in interface builder, give them re-use identifiers(e.g. "Home_1" "Home_2" "Home_3"), and populate the methods as follows:
class HomeViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellIdentifiers:[String] = ["Home_1","Home_2","Home_3"]
let sizes:[CGSize] = [CGSize(width:320, height:260),CGSize(width:320, height:160),CGSize(width:320, height:100)]
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellIdentifiers.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifiers[indexPath.item], for: indexPath)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return sizes[indexPath.item]
}
}
Then set the view controller to be of the proper class, and, hey presto, a (basically) static collection. I'm sorry to say but this is BY FAR the best way to support portrait and landscape views when you have groups of controls...
I did a little experimenting and wanted to add my own method since it helped me achieve the truly static, highly custom Collection View I was looking for.
You can create custom UICollectionViewCells for each cell you want to display in your Collection View, and register them with all the Cell IDs in your Collection View, like this:
Create your static cell:
class MyRedCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .red
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Make as many of these as you want.
And then back in your Collection View Controller, register them with their corresponding cellId:
let cellIds = ["redCell","blueCell","greenCell"]
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(MyRedCell.self, forCellWithReuseIdentifier: "redCell")
collectionView.register(MyBlueCell.self, forCellWithReuseIdentifier: "blueCell")
collectionView.register(MyGreenCell.self, forCellWithReuseIdentifier: "greenCell")
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIds[indexPath.item], for: indexPath)
return cell
}
Each cell will display exactly what's in its class.

Resources