How to display two different cells in UICollectionView in swift3 - ios

I am trying to achieve the following image by using the UICollectionViewCell.
But i have got 3 different cases for this:
A) A particular type of journey only onward journey time is visible.
B) A particular type of journey both onward journey and return journey is visible(as shown in the below image).
C) A particular type of journey has 2 onward journey and 2 return journey time.
So i am trying to use the collection view for this. Am i correct in doing this approach and how to achieve this logic using collection view?

I would probably subclass my UICollectionViewCell, define all necessary layout type enums and pass it in my cellForItemAtIndexPath as a parameter in a custom method, so the custom collection view cell knows how to layout itself.
Something like this:
enum MyLayoutType
{
case layoutA
case layoutB
case layoutC
}
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource
{
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
// MARK: Collection View DataSource
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCustomCVCell", for: indexPath) as! MyCustomCVCell
// Decide here what layout type you would like to set
cell.setUpWithType(layoutType: .layoutA)
return cell
}
}
And then, in your custom collection view cell subclass (don't forget to assign the Custom class in your interface builder file):
class MyCustomCVCell: UICollectionViewCell
{
func setUpWithType(layoutType: MyLayoutType)
{
switch layoutType
{
case .layoutA:
self.backgroundColor = .red
case .layoutB:
self.backgroundColor = .yellow
case .layoutC:
self.backgroundColor = .blue
}
}
}

Related

Prevent `didSelect…` for part of a UICollectionViewCell

Summary
Can a UICollectionViewCell subclass prevent didSelectItemAt: indexPath being sent to the UICollectionViewDelegate for taps on some of its sub views, but to proceed as normal for others?
Use case
I have a UICollectionViewCell that represents a summary of an article. For most articles, when they are tapped, we navigate through to show the article.
However, some article summaries show an inline video preview. When the video preview is tapped, we should not navigate through, but when the other areas of the article summary are tapped (the headline), we should navigate through.
I'd like the article summary cell to be able to decide whether a tap on it should be considered as a selection.
You have to add tapGestureRecogniser on those subviews of cell on which you don't want delegate to get called.
tapGestureRecogniser selector method will get called when you will tap on those subview and gesture will not get passed to delegate.
What you need to do is to attach UITapGestureRecognizer to your view and monitor taps from it:
class MyViewController: UIViewController, UICollectionViewDataSource, MyCellDelegate {
var dataSource: [Article] = []
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ArticleCell", for: indexPath) as! MyCell
cell.article = dataSource[indexPath.row]
cell.delegate = self
return cell
}
func articleDidTap(_ article: Article) {
// do what you need
}
}
// your data model
struct Article {}
protocol MyCellDelegate: class {
func articleDidTap(_ article: Article)
}
class MyCell: UICollectionViewCell {
var article: Article! {
didSet {
// update your views here
}
}
weak var delegate: MyCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(MyCell.tap)))
}
#objc func tap() {
delegate?.articleDidTap(article)
}
}
This should work since your video view should overlap root view and prevent receiving taps from gesture recognizer.

Swift UICollectionView - Add/remove data from another class

I have a main view controller executed first which looks something like below,
MainViewController
class MainViewController: UIViewController {
var collectionView: UICollectionView!
var dataSource: DataSource!
SomeAction().call() {
self.dataSource.insert(message: result!, index: 0)
}
}
DataSource of the collectionview
class DataSource: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var conversation: [messageWrapper] = []
override init() {
super.init()
}
public func insert(message: messageWrapper, index: Int) {
self.conversation.insert(message, at: index)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return conversation.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let textViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "textViewCell", for: indexPath) as! TextCollectionViewCell
let description = conversation[indexPath.row].description
textViewCell.textView.text = description
return textViewCell
}
}
So, when the MainViewController is executed there is one added to the datasource of the collectionview which works perfectly fine.
Problem
Now, I have another class which looks something like
SomeController
open class SomeController {
let dataSource: DataSource = DataSource()
public func createEvent() {
self.dataSource.insert(message: result!, index: 1)
}
}
When I add some data from the above controller, the conversation is empty which doesn't have the existing one record and throw Error: Array index out of range. I can understand that it is because I have again instantiated the DataSource.
How to add/remove data from other class?
Is it the best practice to do it?
Can I make the conversation as global variable?
The Datasource class had been re initialised with it's default nil value, you have to pass the updated class to the next controller to access its updated state.
How to add/remove data from other class?
You should use class Datasource: NSObject {
And your collection view delegates on your viewcontroller class.
pass your dataSource inside prepareForSegue
Is it the best practice to do it?
Yes
Can I make the conversation as global variable?
No, best to use models / mvc style. Data on your models, ui on your viewcontrollers.
It seems your initial count is 1 but you insert at index 1(out of index)
Use self.dataSource.insert(message: result!, index: 0) insteaded
Or use append.

Unknown Class X in Interface Builder File

I am working with Xcode 7 and swift.
I am trying to connect a label that is on a Collection View prototype cell to my code. I know to do this, I have to make a subclass of UICollectionViewCell and put it in that class instead of the view controller, which I did. I run the app immediately after adding the outlet to the subclass, and the app crashes. I noticed at the top of the error message it says: Unknown Class nameOfSubclass in Interface Builder File. I have looked at other stack overflow questions and they say it is a simple matter of setting the module under the custom class. I did this, but the app still crashes and now it says: Unknown class _TtC13 (app name / module name) 17(nameOfSubclass) in Interface Builder file.
identity inspector of the prototype cell
identity inspector of the UICollectionView
Code
import UIKit
class SecondViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
private let reuseIdentifier = "hourlyWeatherCell"
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return 48
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) //as! hourlyWeatherCell
// Configure the cell
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
printDate()
// Do any additional setup after loading the view.
self.collectionView.dataSource = self
self.collectionView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
class hourlyWeatherCell: UICollectionViewCell {
#IBOutlet weak var temperatureHLabel: UILabel!
}
}
With a little tinkering I got it to work. For some reason Xcode was not compiling the subclass it the UIViewContoller. I simply moved the subclass out of the view controller class (making the subclass a class), and everything worked fine.

How to do zoom in UICollectionView

I have working uicollectionview codes with CustomCollectionViewLayout , and inside have a lot of small cells but user cannot see them without zoom. Also all cells selectable.
I want to add my collection view inside zoom feature !
My clear codes under below.
class CustomCollectionViewController: UICollectionViewController {
var items = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
customCollectionViewLayout.delegate = self
getDataFromServer()
}
func getDataFromServer() {
HttpManager.getRequest(url, parameter: .None) { [weak self] (responseData, errorMessage) -> () in
guard let strongSelf = self else { return }
guard let responseData = responseData else {
print("Get request error \(errorMessage)")
return
}
guard let customCollectionViewLayout = strongSelf.collectionView?.collectionViewLayout as? CustomCollectionViewLayout else { return }
strongSelf.items = responseData
customCollectionViewLayout.dataSourceDidUpdate = true
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
strongSelf.collectionView!.reloadData()
})
}
}
}
extension CustomCollectionViewController {
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return items.count
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items[section].services.count + 1
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! CustomCollectionViewCell
cell.label.text = items[indexPath.section].base
return cell
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath cellForItemAtIndexPath: NSIndexPath) {
print(items[cellForItemAtIndexPath.section].base)
}
}
Also my UICollectionView layout properties under below you can see there i selected maxZoom 4 but doesnt have any action !
Thank you !
You don't zoom a collection like you'd zoom a simple scroll view. Instead you should add a pinch gesture (or some other zoom mechanism) and use it to change the layout so your grid displays a different number of items in the visible part of the collection. This is basically changing the number of columns and thus the item size (cell size). When you update the layout the collection can animate between the different sizes, though it's highly unlikely you want a smooth zoom, you want it to go direct from N columns to N-1 columns in a step.
I think what you're asking for looks like what is done in the WWDC1012 video entitled Advanced Collection Views and Building Custom Layouts (demo starts at 20:20) https://www.youtube.com/watch?v=8vB2TMS2uhE
You basically have to add pinchGesture to you UICollectionView, then pass the pinch properties (scale, center) to the UICollectionViewLayout (which is a subclass of UICollectionViewFlowLayout), your layout will then perform the transformations needed to zoom on the desired cell.

iOS separate scrollView & collectionView delegates into individual files

I have a UICollectionView and want to be able to perform custom behaviour when the user scrolls through implementing the scrollView delegate methods. Is it possible to have two separate objects that act as the collectionView delegate and scrollView delegate when working with a collectionView?
You cannot have separate delegates. UICollectionView is a subclass of UIScrollView, and overrides its delegate property to change its type to UICollectionViewDelegate (which is a subtype of UIScrollViewDelegate). So you can only assign one delegate to a collection view, and it may implement any combination of UICollectionViewDelegate methods and UIScrollViewDelegate methods.
However, you can forward the UIScrollViewDelegate methods to another object without much difficulty. Here's how you'd do it in Swift; it would be very similar in Objective-C (since this is all done using the Objective-C runtime):
import UIKit
import ObjectiveC
class ViewController: UICollectionViewController {
let scrollViewDelegate = MyScrollViewDelegate()
override func respondsToSelector(aSelector: Selector) -> Bool {
if protocol_getMethodDescription(UIScrollViewDelegate.self, aSelector, false, true).types != nil || protocol_getMethodDescription(UIScrollViewDelegate.self, aSelector, true, true).types != nil {
return scrollViewDelegate.respondsToSelector(aSelector)
} else {
return super.respondsToSelector(aSelector)
}
}
override func forwardingTargetForSelector(aSelector: Selector) -> AnyObject? {
if protocol_getMethodDescription(UIScrollViewDelegate.self, aSelector, false, true).types != nil || protocol_getMethodDescription(UIScrollViewDelegate.self, aSelector, true, true).types != nil {
return scrollViewDelegate
} else {
return nil
}
}
Note that MyScrollViewDelegate probably has to be a subclass of NSObject for this to work.
If I understand you correctly, then you just need your view controller to subclass UICollectionViewController or UICollectionViewDelegate. Then you can access the scrollView delegate methods since they are inherited by the collectionView
Create subclass of UICollectionViewController and write scroll view delegates into it.
class CustomCollectionViewController: UICollectionViewController {
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
}
}
In your target class
class MyCollectionViewController: CustomCollectionViewController, UICollectionViewDelegateFlowLayout {
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
return cell
}
}

Resources