i have a problem and confusing i want to ask how can i make a new object ( i want to make date ) under the icons, and under the date there's icon again.. like gallery on iPhone,
in example:
august
(photos)
september
(photos)
and so on..thx
will be looks like this, but how
there is my code in this view
import UIKit
let reuseIdentifier = "Cell"
class SummaryViewController: UICollectionViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet var collectionview: UICollectionView!
var photos:NSArray?
var items = NSMutableArray()
var TableData:Array< String > = Array < String >()
var json:String = ""
var arrayOfMenu: [ImageList] = [ImageList]()
override func viewDidLoad() {
super.viewDidLoad()
self.setUpMenu()
collectionview.dataSource = self
collectionview.delegate = self
NSLog("%d", items.count)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrayOfMenu.count //hitung banyak data pada array
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! UICollectionViewCell
let image = UIImage(named: items.objectAtIndex(indexPath.row) as! String)
let imageView = cell.viewWithTag(100) as! UIImageView
imageView.image = image
return cell
}
func setUpMenu() //membaca json pada setiap arraynya
{
var json: JSON = JSON (data: NSData())
DataManager.getactivityDataFromFileWithSuccess{ (data) -> Void in
json = JSON(data: data)
let results = json["results"]
for (index: String, subJson: JSON) in results {
}
for (var i = 0; i < json["Activity"].count; i++) {
if let icon: AnyObject = json["Activity"][i]["icon"].string {
self.items.addObject(icon)
dispatch_async(dispatch_get_main_queue(), {self.collectionView!.reloadData()})
var menu = ImageList(image: icon as! String)
self.arrayOfMenu.append(menu)
self.TableData.append(icon as! String)
}
}
}
}
override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView
{
let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "headersection", forIndexPath: indexPath) as! UICollectionReusableView
return header
}
}
You can set number of sections to required number of months.
Like this :
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int
{
return 3
}
And for the menu, you need to give it according to section(month).
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
if section == 0
{
return arrayOfFirstMenu.count
}
else if section == 1
{
return arrayOfSecondMenu.count
}
else
{
return arrayOfThirdMenu.count
}
}
Hope this helps!
Related
I have fetched and parsed my Data from API in JSON format to FirstViewController and want to segue to the SecondViewController with the data selected at the concrete person from FirstViewController. The Problem is that I have an API with such example URL: https://www.example.com/api/?action=persons&ln=en which gives me all persons in this format:
[
{
"p_id": "4107",
"p_name": "Name1 Surname1",
"p_role": "Role1",
"general_image": "/imagedb/persons/4107/main/1.jpg"
},{
"p_id": "1978",
"p_name": "Name2 Surname2",
"p_role": "Role2",
"general_image": "/imagedb/persons/1978/main/1.jpg"
}, {...
...}
]
I am showing all these persons in my FirstViewController in CollectionView, which is working correctly, with images, names, roles. But also I need to show my SecondViewController with the data selected in FirstVC. My API for personByID is like this URL: https://www.example.com/api/?action=person&ln=en&personId=1978 which gives me JSON Data in this format:
{
"p_id": "1978",
"p_category": "[2]",
"p_name": "Name2 Surname2",
"p_role": "Role2",
"p_short": null,
"p_text": "long text...",
"p_date_start": "1922.02.05",
"p_date_end": "",
"p_profile_image": "1",
"p_status": "1",
"p_lang": "en",
"general_image": "/imagedb/persons/1978/main/1.jpg",
"photos": [
{
"image_id": "5",
"p_id": "1978",
"lang": "en",
"text": "some text...",
"general": "/imagedb/persons/1978/5.jpg",
"thumbs": "/imagedb/persons/1978/thumb/5.jpg"
},
{
"image_id": "7",
"p_id": "1978",
"lang": "en",
"text": "some text...",
"general": "/imagedb/persons/1978/7.jpg",
"thumbs": "/imagedb/persons/1978/thumb/7.jpg"
}
]
}
This is my Person Struct:
struct Person {
let id: String
let name: String
let role: String
fileprivate let imageURLString: String
var imageURL: URL? {
return URL(string: "https://www.example.com\(imageURLString)")
}
}
extension Person: JSONDecodable {
init(_ decoder: JSONDecoder) throws {
self.id = try decoder.value(forKey: "p_id")
self.name = try decoder.value(forKey: "p_name")
self.role = try decoder.value(forKey: "p_role")
self.imageURLString = try decoder.value(forKey: "general_image")
}
}
This is My FIRST VC:
import UIKit
class PersonListViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var personPages: [PagedResult<Person>] = [] {
didSet {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadPersons()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first else {
return
}
collectionView.deselectItem(at: selectedIndexPath, animated: animated)
}
var service = ExampleWebService()
private func loadPersons(page: Int = 0, resultsPerPage: Int = 5) {
service.persons(page: page, resultsPerPage: resultsPerPage) { (personPage) in
guard !self.loadedPersonPageNumbers.contains(page) else { return }
self.personPages.append(personPage)
self.updateLastIndexPath(personPage)
}
}
private(set) var lastIndexPath: IndexPath?
private func updateLastIndexPath(_ personPage: PagedResult<Person>) {
if personPage.results.isEmpty {
lastIndexPath = nil
}
else {
lastIndexPath = calculateLastIndexPath()
}
}
private func calculateLastIndexPath() -> IndexPath? {
guard let lastPage = personPages.last else { return nil }
let section = lastPage.pageNumber
let row = lastPage.results.count - 1
return IndexPath(row: row, section: section)
}
fileprivate var loadedPersonPageNumbers: [Int] {
return personPages.map { $0.pageNumber }
}
func person(at indexPath: IndexPath) -> Person? {
guard indexPath.section < personPages.count else {
return nil
}
guard indexPath.row < personPages[indexPath.section].results.count else {
return nil
}
let page = personPages[indexPath.section]
return page.results[indexPath.row]
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let personViewController = segue.destination as? PersonViewController,
let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first else {
return
}
personViewController.person = person(at: selectedIndexPath)
}
#IBAction func exitToPersonsView(segue: UIStoryboardSegue) {
}
}
extension PersonListViewController: UICollectionViewDelegate {
fileprivate var nextPageIndex: Int {
guard let lastPage = personPages.last else {
return 0
}
return lastPage.pageNumber.advanced(by: 1)
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath == lastIndexPath {
loadPersons(page: nextPageIndex)
}
}
}
extension PersonListViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return personPages.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return personPages[section].results.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: PersonListCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! PersonListCollectionViewCell
cell.person = person(at: indexPath)
return cell
}
}
extension PersonListViewController: UINavigationBarDelegate {
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
And this is My Second VC:
import Foundation
import UIKit
final class PersonViewController: UIViewController {
#IBOutlet weak var imagesCollectionVIew: UICollectionView!
#IBOutlet weak var personRole: UILabel!
#IBOutlet weak var customNavigationBar: UINavigationBar!
var personImagesByID: [PagedResult<Person>] = [] {
didSet {
DispatchQueue.main.async {
self.imagesCollectionVIew.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadPersonImagesByID()
}
var person: Person?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let person = person {
title = person.name
personRole.text = person.role
}
customNavigationBar.topItem?.title = title
}
var service = ExampleWebService()
private func loadPersonImagesByID(page: Int = 0, resultsperPge: Int = 5) {
service.persons(page: page, resultsPerPage: resultsperPge) { (personPage) in
guard !self.loadedPersonPageNumbers.contains(page) else {
return
}
self.personImagesByID.append(personPage)
self.updateLastIndexPath(personPage)
}
}
private(set) var lastIndexPath: IndexPath?
private func updateLastIndexPath(_ personPage: PagedResult<Person>) {
if personPage.results.isEmpty {
lastIndexPath = nil
}
else {
lastIndexPath = calculateLastIndexPath()
}
}
private func calculateLastIndexPath() -> IndexPath? {
guard let lastPage = personImagesByID.last else {
return nil
}
let section = lastPage.pageNumber
let item = lastPage.results.count - 1
return IndexPath(row: item, section: section)
}
fileprivate var loadedPersonPageNumbers: [Int] {
return personImagesByID.map { $0.pageNumber }
}
func person(at indexPath: IndexPath) -> Person? {
guard indexPath.section < personImagesByID.count else {
return nil
}
guard indexPath.item < personImagesByID[indexPath.section].results.count else {
return nil
}
let page = personImagesByID[indexPath.section]
return page.results[indexPath.item]
}
}
extension PersonViewController: UICollectionViewDelegate {
fileprivate var nextPageIndex: Int {
guard let lastPage = personImagesByID.last else {
return 0
}
return lastPage.pageNumber.advanced(by: 1)
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath == lastIndexPath {
loadPersonImagesByID(page: nextPageIndex)
}
}
}
extension PersonViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.imagesCollectionVIew.frame.height - 17, height: self.imagesCollectionVIew.frame.height - 17)
}
}
extension PersonViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return personImagesByID.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return personImagesByID[section].results.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: PersonViewCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImagesCollectionViewCell", for: indexPath) as! PersonViewCollectionViewCell
cell.person = person(at: indexPath)
return cell
}
}
extension PersonViewController: UINavigationBarDelegate {
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
Now my issue is that I am having a problem of how to write correctly my second struct PersonByID and when clicking on the person from First VC to show me data in Second VC from personByID URL Path.
My didSelectItemAt method is not being called and nothing is being printed into the console. I have user interaction turned on and I still can not get it to print out anything. I am not sure if my custom PinterestStyle Layout is causing this or if I am missing something. The ultimate goal would be to segue into a detail view controller showing the profile page of the cell selected. I will do that using prepareForSegue however I still can't even get it to print out the name of the cell when tapped.
class PagesCollectionViewController: UICollectionViewController, firebaseHelperDelegate {
var storageRef: StorageReference!{
return Storage.storage().reference()
}
var usersList = [String]()
var authService : FirebaseHelper!
var userArray : [Users] = []
var images: [UIImage] = []
var names: [String] = []
override func viewWillAppear(_ animated: Bool) {
if Global.Location != "" && Global.Location != nil
{
usersList = Global.usersListSent
print(usersList)
self.authService.ListOfUserByLocation(locationName: Global.Location, type: .ListByLocation)
}
}
override func viewDidLoad() {
self.collectionView?.allowsSelection = true
self.collectionView?.isUserInteractionEnabled = true
super.viewDidLoad()
self.authService = FirebaseHelper(viewController: self)
self.authService.delegate = self
setupCollectionViewInsets()
setupLayout()
}
private func setupCollectionViewInsets() {
collectionView!.backgroundColor = .white
collectionView!.contentInset = UIEdgeInsets(
top: 20,
left: 5,
bottom: 49,
right: 5
)
}
private func setupLayout() {
let layout: PinterestLayout = {
if let layout = collectionViewLayout as? PinterestLayout {
return layout
}
let layout = PinterestLayout()
collectionView?.collectionViewLayout = layout
return layout
}()
layout.delegate = self
layout.cellPadding = 5
layout.numberOfColumns = 2
}
func firebaseCallCompleted(data: AnyObject?, isSuccess: Bool, error: Error?, type: FirebaseCallType) {
if(type == .ListByLocation) {
if(isSuccess) {
self.userArray.removeAll()
self.images.removeAll()
self.images.removeAll()
if(data != nil) {
let dataDict = data as! NSDictionary
let keyArray = dataDict.allKeys
for i in 0 ..< keyArray.count {
var dict = NSDictionary()
dict = dataDict.object(forKey: keyArray[i]) as! NSDictionary
self.userArray.append(Users.init(data: dict))
}
}
self.collectionView?.reloadData()
}
else {
print(error?.localizedDescription)
SVProgressHUD.dismiss()
}
}
}
}
extension PagesCollectionViewController {
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userArray.count
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(userArray[indexPath.row].name)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: "PagesCollectionViewCell",
for: indexPath) as! PagesCollectionViewCell
cell.nameLabel.text = userArray[indexPath.row].name
if let imageOld = URL(string: userArray[indexPath.row].photoURL){
cell.photo.sd_setImage(
with: imageOld,
placeholderImage: nil,
options: [.continueInBackground, .progressiveDownload]
)
}
return cell
}
}
extension PagesCollectionViewController : PinterestLayoutDelegate {
func collectionView(collectionView: UICollectionView,
heightForImageAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat {
var image: UIImage?
let url = URL(string: userArray[indexPath.row].photoURL)
let data = try? Data(contentsOf: url!)
image = UIImage(data: data!)
return (image?.height(forWidth: withWidth))!
}
func collectionView(collectionView: UICollectionView,
heightForAnnotationAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat {
return 30
}
}
Check 2 conditions:-
Make sure you have set delegate to UICollectionView
Make sure Content in PageCollectionCell like image having no user interaction enabled. If image user interaction is enabled then didSelectItemAt will not call.
as Manish Mahajan said a quick fix would be:
in cellForItemAt func set contentView as not clickable
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.contentView.isUserInteractionEnabled = false
return cell
}
I'm working on an onboarding flow for my iOS App in Swift. I'd like to allow users to tap other users in a collection view and have it follow those users. I need the collection view to be able to allow multiple cells to be selected, store the cells in an array and run a function once the users taps the next button. Here's my controller code:
class FollowUsers: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var tableData: [SwiftyJSON.JSON] = []
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var loadingView: UIView!
private var selectedUsers: [SwiftyJSON.JSON] = []
override func viewDidLoad() {
super.viewDidLoad()
self.getCommunities()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getUsers() {
Alamofire.request(.GET, "url", parameters: parameters)
.responseJSON {response in
if let json = response.result.value {
let jsonObj = SwiftyJSON.JSON(json)
if let data = jsonObj.arrayValue as [SwiftyJSON.JSON]? {
self.tableData = data
self.collectionView.reloadData()
self.loadingView.hidden = true
}
}
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.tableData.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: UserViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("userCell", forIndexPath: indexPath) as! UserViewCell
let rowData = tableData[indexPath.row]
if let userName = rowData["name"].string {
cell.userName.text = userName
}
if let userAvatar = rowData["background"].string {
let url = NSURL(string: userAvatar)
cell.userAvatar.clipsToBounds = true
cell.userAvatar.contentMode = .ScaleAspectFill
cell.userAvatar.hnk_setImageFromURL(url!)
}
cell.backgroundColor = UIColor.whiteColor()
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell: UserViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("userCell", forIndexPath: indexPath) as! UserViewCell
let rowData = tableData[indexPath.row]
let userName = rowData["name"].string
let userId = rowData["id"].int
selectedUsers.append(rowData[indexPath.row])
print("Cell \(userId) \(userName) selected")
}
}
override func viewDidLoad() {
super.viewDidLoad()
collection.dataSource = self
collection.delegate = self
collection.allowsMultipleSelection = true
self.getCommunities()
}
You should be able to make multiple selections with this.
how to make this become looks like iPhone gallery (if the data meets the end, i want to add the label which is shows the month) and make it repeatable
it become looks like this
i've tried every objects and insert to uicollectionview, but it doesn't appear at all..is there any way to help?
here is my code
import UIKit
let reuseIdentifier = "Cell"
class SummaryViewController: UICollectionViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet var collectionview: UICollectionView!
var photos:NSArray?
var items = NSMutableArray()
var TableData:Array< String > = Array < String >()
var json:String = ""
var arrayOfMenu: [ImageList] = [ImageList]()
override func viewDidLoad() {
super.viewDidLoad()
self.setUpMenu()
collectionview.dataSource = self
collectionview.delegate = self
NSLog("%d", items.count)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrayOfMenu.count //hitung banyak data pada array
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! UICollectionViewCell
let image = UIImage(named: items.objectAtIndex(indexPath.row) as! String)
let imageView = cell.viewWithTag(100) as! UIImageView
imageView.image = image
return cell
}
func setUpMenu() //membaca json pada setiap arraynya
{
var json: JSON = JSON (data: NSData())
DataManager.getactivityDataFromFileWithSuccess{ (data) -> Void in
json = JSON(data: data)
let results = json["results"]
for (index: String, subJson: JSON) in results {
}
for (var i = 0; i < json["Activity"].count; i++) {
if let icon: AnyObject = json["Activity"][i]["icon"].string {
self.items.addObject(icon)
dispatch_async(dispatch_get_main_queue(), {self.collectionView!.reloadData()})
var menu = ImageList(image: icon as! String)
self.arrayOfMenu.append(menu)
self.TableData.append(icon as! String)
}
}
}
}
override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView
{
let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "headersection", forIndexPath: indexPath) as! UICollectionReusableView
return header
}
}
when I try to create a generic class which implement UICollectionViewDataSource in swift it say that my class does not conform to protocol (and sometime Xcode crash).
Does it mean that we can't create generic data provider for UICollectionView and that we have to duplicate code ?
Here is the generic code :
// Enum protocol
protocol OptionsEnumProtocol
{
typealias T
static var allValues:[T] {get set}
var description: String {get}
func iconName() -> String
}
// enum : list of first available options
enum Options: String, OptionsEnumProtocol
{
typealias T = Options
case Color = "Color"
case Image = "Image"
case Shadow = "Shadow"
static var allValues:[Options] = [Color, Image, Shadow]
var description: String {
return self.rawValue
}
func iconName() -> String
{
var returnValue = ""
switch(self)
{
case .Color: returnValue = "color_icon"
case .Image: returnValue = "image_icon"
case .Shadow: returnValue = "shadow_icon"
}
return returnValue
}
}
// class to use as the uicollectionview datasource and delegate
class OptionsDataProvider<T>: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
private let items = T.allValues
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return items.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(OptionsCellReuseIdentifier, forIndexPath: indexPath) as! GenericIconLabelCell
let item = self.items[indexPath.row]
// Configure the cell
cell.iconFileName = item.iconName()
cell.labelView.text = item.description
return cell
}
}
But because it failed I have to use this non generic form instead :
enum Options: String
{
case Color = "Color"
case Image = "Image"
case Shadow = "Shadow"
static var allValues:[Options] = [Color, Image, Shadow]
var description: String {
return self.rawValue
}
func iconName() -> String
{
var returnValue = ""
switch(self)
{
case .Color: returnValue = "color_icon"
case .Image: returnValue = "image_icon"
case .Shadow: returnValue = "shadow_icon"
}
return returnValue
}
}
class OptionsDataProvider: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
private let items = Options.allValues
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return items.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(OptionsCellReuseIdentifier, forIndexPath: indexPath) as! GenericIconLabelCell
let item = self.items[indexPath.row]
// Configure the cell
cell.iconFileName = item.iconName()
cell.labelView.text = item.description
return cell
}
}
which obligate me to duplicate the class for each enum type I have.
Exact error :
You are right, it is not possible to write a generic class. However, I have found a workaround. It doesn't use enums and so maybe you don't find it very useful. However, it achieves what you want - you are getting a collection view data source which can be used with different classes providing necessary data. Here is the code:
protocol OptionsProviderProtocol
{
func allValues() -> [OptionsItem]
}
class OptionsItem:NSObject {
let itemDescription:String
let iconName:String
init(iconName:String,description:String) {
self.itemDescription = description
self.iconName = iconName
}
}
// class stores first available options
class Options: NSObject, OptionsProviderProtocol
{
let color = OptionsItem(iconName: "color_icon", description: "Color")
let image = OptionsItem(iconName: "image_icon", description: "Image")
let shadow = OptionsItem(iconName: "shadow_icon", description: "Shadow")
func allValues() -> [OptionsItem] {
return [color, image, shadow]
}
}
// class to use as the uicollectionview datasource and delegate
class OptionsDataProvider: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
private var items:[OptionsItem] = []
convenience init(optionsProvider:OptionsProviderProtocol) {
self.items = optionsProvider.allValues()
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return items.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(OptionsCellReuseIdentifier, forIndexPath: indexPath) as! GenericIconLabelCell
let item = self.items[indexPath.row]
// Configure the cell
cell.iconFileName = item.iconName()
cell.labelView.text = item.description
return cell
}
}
If you have any questions please let me know.
When you inherit from a protocol you must implement all required methods. Swift 2 will change this somewhat. Perhaps you really want to inherit from a class.
I had the similar problem/question when I was trying to inherit Generic class from NSOperation class. xCode didn't give me a compile error because there were no protocols involved, instead my override func main() was simply never called :)
Anyway... If you follow workaround that mr. Topal Sergey advised, you can achieve exactly what you want relatively easily.
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView?
private var defaultDataProvider = OptionsDataProvider<Options>()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
collectionView?.delegate = defaultDataProvider
collectionView?.dataSource = defaultDataProvider
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// Enum protocol
protocol OptionsEnumProtocol {
static var allValues: [OptionsEnumProtocol] {get set}
var description: String {get}
func iconName() -> String
}
// enum : list of first available options
enum Options: String, OptionsEnumProtocol {
case Color = "Color"
case Image = "Image"
case Shadow = "Shadow"
static var allValues: [OptionsEnumProtocol] = [Color, Image, Shadow]
var description: String {
return self.rawValue
}
func iconName() -> String
{
var returnValue = ""
switch(self)
{
case .Color: returnValue = "color_icon"
case .Image: returnValue = "image_icon"
case .Shadow: returnValue = "shadow_icon"
}
return returnValue
}
}
class OptionsDataProviderWrapper: NSObject, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
// MARK: protocols' funcs
final func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return wrapperCollectionView(collectionView, numberOfItemsInSection: section)
}
final func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
return wrapperCollectionView(collectionView, cellForItemAtIndexPath: indexPath)
}
// MARK: for override
func wrapperCollectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 0
}
func wrapperCollectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
return UICollectionViewCell()
}
}
class OptionsDataProvider<T: OptionsEnumProtocol>: OptionsDataProviderWrapper {
private let items = T.allValues
override func wrapperCollectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
override func wrapperCollectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("reuseId", forIndexPath: indexPath) as! GenericIconLabelCell
let item = self.items[indexPath.row]
cell.labelView?.text = item.description
return cell
}
}
class GenericIconLabelCell: UICollectionViewCell {
#IBOutlet weak var labelView: UILabel?
}
The key here is to create OptionsDataProviderWrapper that is not a generic and implements all your protocols. The only thing that it does - it redirects calls to another functions like func wrapperCollectionView...
Now you can inherit your Generic class from this OptionsDataProviderWrapper and override that wrapper functions.
Note: you have to override exactly wrapper functions because native func collectionView... functions will not be called in your generic subclass similarly to my issue with NSOperation. That's why I marked native functions with final.