Reach CollectionViewCell into Xib with Programatically - ios

I created a Xib File and call in ViewController.Also I create a CollectionView into Xib File.And now I want to reach CollectionViewCell for showing cells.
class ProductVC: UIViewController {
var collection:UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let productView : ProductDetailView = UIView.fromNib()
productView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(productView)
collection = productView.collectionView
collection.register(UINib(nibName: "TagCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "TagCollectionViewCell")
productView.topAnchor.constraint(equalTo: view.safeTopAnchor).isActive = true
productView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
productView.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
}
}
class ProductDetailView: UIView {
#IBOutlet var productTitle: UILabel!
#IBOutlet var dateLabel: UILabel!
#IBOutlet var productImage: UIImageView!
#IBOutlet var productDescriptionLabel: UILabel!
#IBOutlet var collectionView: UICollectionView!
}
class TagCollectionViewCell: UICollectionViewCell {
#IBOutlet var tagName: UILabel!
}
Also I added some code like below . But has no sense!. Where is my mistake?
extension ProductVC : UICollectionViewDelegate , UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell: TagCollectionViewCell! = collectionView.dequeueReusableCell(withReuseIdentifier: "TagCollectionViewCell", for: indexPath) as? TagCollectionViewCell
if cell == nil {
collectionView.register(UINib(nibName: "TagCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "TagCollectionViewCell")
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TagCollectionViewCell", for: indexPath) as? TagCollectionViewCell
}
cell.tagName.text = "aa"
return cell
}
}

You did not conform to delegate and dataSource protocols. I think this the problem. Put below lines after collection.register
collection.delegate = self
collection.dataSource = self
Hope this will work.

Related

Pass data from CollectionView to TabBarController, than to his childs in Swift

I am new to Swift. Unable to find solution for below problem.
Below is a ViewController with CollectionView and When you click on Cell in CollectionView, data from cell(even this who isn't in label and image view, but are in Book array row) must be send to TabBarCollection, than from TabBarCollection I need to send this data to all of child's, like in this image.
Later in childs of TabBar I will set value of Labels in View Controllers from data from choosed Cell.
Book.swift
import UIKit
struct Book {
let title: String
let image: UIImage?
//Here soon will be more "let", and this data will also have to be send to TabBar but it don't will be show I CollectionViewCell
}
extension Book {
static let books: [Book] = [
Book(title: "Antygona", image: UIImage(named: "imgantygona")!),
//etc
]
}
CollectionViewCell.swift
import UIKit
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var bookImageView: UIImageView!
#IBOutlet weak var bookTitle: UILabel!
func setup(with book: Book) {
bookTitle.text = book.title
bookImageView.image = book.image
}
}
ViewController
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
let books = Book.books
override func viewDidLoad() {
super.viewDidLoad()
let fontAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16.0)]
UITabBarItem.appearance().setTitleTextAttributes(fontAttributes, for: .normal)
collectionView.dataSource = self
collectionView.delegate = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return books.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "bookCell", for: indexPath) as! CollectionViewCell
let book = books[indexPath.item]
cell.setup(with: book)
return cell
}
}
I saw many solutions but I can't perfectly adapt it to my problem. :(
Thanks for help !
BookInsideViewController.swift
import UIKit
class BookInsideViewController: UIViewController {
#IBOutlet weak var testImageView: UIImageView!
#IBOutlet weak var testLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
}
You can use collection view DidSelectItemAt function
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "YourtabbarIdentifier") as! UITabBarController
// You should access the `imageController` through your `tabBarController`,
// not instantiate it from storyboard.
if let viewControllers = tabBarController.viewControllers,
let imageController = viewControllers.first as? ImageController {
BookInsideViewController.recivedData1 = Books[indexPath.row]
}
navigationController?.pushViewController(tabBarController, animated: true)
}

NSUnknownKeyException when using an IBOutlet in UICollectionViewCell in .xib

I've been trying to add a label using an IBOutlet from an xib to a custom UICollectionViewCell class that is meant to be used as the cells for a UICollectionView that I have in another xib but when I add the outlet I get an NSUnknownKeyException on the label reference I've created, without the outlet reference the contents of the cell load properly but I want to be able to manipulate the label within the cell.
Heres what I have in my parent xib class:
class Calendar : UIView, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
let nibName: String = "Calendar"
let calendarDayNibName: String = "CalendarDay"
let calendarReusableCellName: String = "CalendarCell"
let calendarDaysLimit: Int = 35
var contentView: UIView?
#IBOutlet var sundayLabel: UILabel!
#IBOutlet var mondayLabel: UILabel!
#IBOutlet var tuesdayLabel: UILabel!
#IBOutlet var wednesdayLabel: UILabel!
#IBOutlet var thursdayLabel: UILabel!
#IBOutlet var fridayLabel: UILabel!
#IBOutlet var saturdayLabel: UILabel!
#IBOutlet var calendarDays: UICollectionView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
guard let view = self.loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view)
contentView = view
contentView?.isUserInteractionEnabled = true
}
override func awakeFromNib()
{
super.awakeFromNib()
calendarDays.register(UINib(nibName: calendarDayNibName, bundle: nil), forCellWithReuseIdentifier:calendarReusableCellName)
calendarDays.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return calendarDaysLimit;
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: calendarReusableCellName, for: indexPath)
configureCell(cell: cell)
return cell
}
private func configureCell(cell: UICollectionViewCell)
{
//does nothing right now, placeholder for if configuring cell on
//collection view load
}
private func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
self.isUserInteractionEnabled = true
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
public func loadCalendarDays(month: Int)
{
//todo: write day loading logic here
}
}
Here is the child xib class (UICollectionViewCell):
class CalendarDay: UICollectionViewCell
{
#IBOutlet weak var dayLabel: UILabel!
}
Here is my overall project if it helps to look at: https://github.com/CharlemagneVI/practice-calendar
You've set the classes and IBOutlet connections wrong... well, not-quite-right...
In CalendarDay.xib, the class of File's Owner should be the default NSObject:
and it should not have any connections:
The class of the cell object itself should be CalendarDay:
and that is where you make your connection:
That should do it :)

How to call collectionView.reloadData() from reusable header button?

I have a button in my reusable header view that deletes information in the array that supplies the data for the collection view and header. I want it to also be able to perform collectionView.reloadData().
The issue is that I can't call collectionView.reloadData() from the header button because it doesn't recognize collectionView as a variable. If I call Builds().collectionView.reloadData() (Builds is the View controller) the app crashes because it says that it found nil while unwrapping optional. I know that simply calling collectionView.reloadData() isn't the problem because i have collectionView.reloadData() called in viewDidAppear() and that gives me no crashes.
Whats going on here? How can I get my collectionView to reload data after the button removes the data?
For reference:
Reusable Header:
import UIKit
class BuildsHeader: UICollectionReusableView {
#IBOutlet weak var headerLabel: UILabel!
#IBOutlet weak var deleteBuildButton: UIButton!
#IBAction func deleteBuildButton(sender: UIButton) {
for hero in heroes{ if hero.hero.name == heroForDetails.hero.name {
hero.builds.removeAtIndex(sender.tag)
heroForDetails = hero
}}
saveToDefaults(userProfile)
//Builds().collectionBuild.reloadData() runs just fine without this line
}
}
ViewController:
import UIKit
class Builds: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionBuild: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionBuild.delegate = self
collectionBuild.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(animated: Bool) {
collectionBuild.reloadData()
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("BuildCell", forIndexPath: indexPath) as? TalentsCell {
let build = heroForDetails.builds[indexPath.section]
switch (indexPath.row) {
case 0:
cell.configureCell(build["1"]! as! Talent)
case 1:
cell.configureCell(build["4"]! as! Talent)
case 2:
cell.configureCell(build["7"]! as! Talent)
case 3:
cell.configureCell(build["10"]! as! Talent)
case 4:
cell.configureCell(build["13"]! as! Talent)
case 5:
cell.configureCell(build["16"]! as! Talent)
case 6:
cell.configureCell(build["20"]! as! Talent)
default:
cell.configureCell(build["1"]! as! Talent)
}
return cell
}
return UICollectionViewCell()
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return heroForDetails.builds.count
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(50,50)
}
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "BuildsHeader", forIndexPath: indexPath) as! BuildsHeader
var buildDict = heroForDetails.builds[indexPath.section]
header.headerLabel.text = buildDict["name"] as! String
header.deleteBuildButton.tag = indexPath.section
return header
}
}
You can achieve that with delegation.
Create a protocol
Create a variable inside BuildsHeader for delegate
Call the delegate method in deleteBuildButton function
Now the code for BuildsHeader should look like this:
import UIKit
//**** The Protocol ****
protocol BuildsHeaderDelegate {
func updateCollectionView()
}
class BuildsHeader: UICollectionReusableView {
//**** Variable for the delegate ****
var delegate: BuildsHeaderDelegate?
#IBOutlet weak var headerLabel: UILabel!
#IBOutlet weak var deleteBuildButton: UIButton!
#IBAction func deleteBuildButton(sender: UIButton) {
for hero in heroes{ if hero.hero.name == heroForDetails.hero.name {
hero.builds.removeAtIndex(sender.tag)
heroForDetails = hero
}}
saveToDefaults(userProfile)
//**** Call the delegate method ****
self.delegate?.updateCollectionView()
}
}
In the viewForSupplementaryElementOfKind method of your collection view configure the delegate property of the header, like this:
let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "BuildsHeader", forIndexPath: indexPath) as! BuildsHeader
//**** Set this view controller to be the header's delegate ****
header.delegate = self
// rest of header setup
Make the Builds ViewController confirm to BuildsHeaderDelegate :
class Builds: UIViewController, BuildsHeaderDelegate, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
Implement BuildsHeaderDelegate's delegate method in the Builds view controller. you can put this right after your viewForSupplementaryElementOfKind method:
func updateCollectionView () {
//**** reload the collectionView ****
self.collectionBuild.reloadData()
}
Use Delegate to call the method:
protocol BuildsHeaderDelegate(){
func reloadCollectionView()
}
class BuildsHeader: UICollectionReusableView {
var delegate:BuildsHeaderDelegate!
#IBOutlet weak var headerLabel: UILabel!
#IBOutlet weak var deleteBuildButton: UIButton!
#IBAction func deleteBuildButton(sender: UIButton) {
for hero in heroes{ if hero.hero.name == heroForDetails.hero.name {
hero.builds.removeAtIndex(sender.tag)
heroForDetails = hero
}}
saveToDefaults(userProfile)
//##Call delegate method##
delegate.reloadCollectionView()
}
}
Conform delegate in View Controller and Assign it:
class Builds: UIViewController,delegate:BuildsHeaderDelegate , UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionBuild: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collectionBuild.delegate = self
collectionBuild.dataSource = self
}
func reloadCollectionView() {
collectionBuild.reloadData()
}
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryViewOfKind(UICollectionElementKindSectionHeader, withReuseIdentifier: "BuildsHeader", forIndexPath: indexPath) as! BuildsHeader
//##Assign Delegate##
header.delegate = self
var buildDict = heroForDetails.builds[indexPath.section]
header.headerLabel.text = buildDict["name"] as! String
header.deleteBuildButton.tag = indexPath.section
return header
}
}

reloading visible uicollectionviewcell when nested in uitableviewcell

I have a UICollection that shows a padlock on cells that locked to users who aren't logged in. The user can view the collection and then login in a modal. When the modal dismisses, I am trying to reload the cells of the table and the nested collection to remove the padlocks from the cells.
The visible cells are not refreshing to remove the padlock. When the collection is scrolled, the cells offscreen are correct and show with padlock. I am calling reloaddata() on both the tableview and each nested collectionview.
The code I have is separated to:
UIViewController
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("SectionWorkouts", forIndexPath: indexPath) as! SectionTableViewCell
cell.delegate = self
// Return the workouts count from the section
let intIndex = indexPath.row
let index = workoutSections.startIndex.advancedBy(intIndex)
let currentWorkoutSectionKey = workoutSections.keys[index]
if let currentWorkoutSection = workoutSections[currentWorkoutSectionKey] {
cell.workoutsCollection.dataSource = sectionWorkoutsCell
cell.workoutsCollection.delegate = sectionWorkoutsCell
cell.updateCellWithWorkouts(currentWorkoutSectionKey, workouts: currentWorkoutSection)
}
}
return cell
}
UITableViewCell
class SectionTableViewCell: UITableViewCell,UICollectionViewDelegate, UICollectionViewDataSource {
var workouts = [Workout]()
var delegate: WorkoutCellDelegate?
#IBOutlet weak var sectionTitle: UILabel!
#IBOutlet weak var workoutsCollection: UICollectionView!
func updateCellWithWorkouts(title: String, workouts: [Workout]){
self.sectionTitle.text = title
self.workouts = workouts
dispatch_async(dispatch_get_main_queue(),{
self.workoutsCollection.reloadData()
})
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("SectionWorkoutCell", forIndexPath: indexPath) as! SectionCollectionViewCell
let row = indexPath.row
let workout = workouts[row]
cell.configCell(workout)
return cell
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return workouts.count
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let row = indexPath.row
let feature = workouts[row]
if let delegate = self.delegate{
delegate.didSelectWorkoutCell(feature)
}
}
}
UICollectionViewCell
class SectionCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageContainer: UIView!
#IBOutlet weak var image: UIImageView!
#IBOutlet weak var tintOverlay: UIView!
#IBOutlet weak var padlock: UIImageView!
#IBOutlet weak var workoutTitle: UILabel!
#IBOutlet weak var duration: UILabel!
var locked = true
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func configCell(workout: Workout){
self.workoutTitle.text = workout.name
if workout.type == "Free" || AccountManager.userIsLoggedInMember() {
self.setToUnLocked()
}else{
self.setToLocked()
}
self.layoutIfNeeded()
}
func setToUnLocked(){
locked = false
tintOverlay.alpha = 0
padlock.alpha = 0
}
func setToLocked(){
locked = true
tintOverlay.alpha = 0.6
padlock.alpha = 1
}
}
You should probably move the call to configure the cell to the willDisplayCell: method instead. You can remove it from the cellForItemAtIndexPath method. This is the correct time to configure any visual aspects of the cell to be displayed.
func collectionView(collectionView: UICollectionView,
willDisplayCell cell: UICollectionViewCell,
forItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let row = indexPath.row
let workout = workouts[row]
cell.configCell(workout)
return cell
}
The problem lies in the fact that:
the tableViewCell owns the data that is used for configuring the collectionViewCell.
If you want your collectionViewCell to update without the need for tableView.reloadData, the data that is used for configuring the collectionViewCell(in your case 'workout') must be fetched from elsewhere than from the tableViewCell.

How to instantiate a ViewController from a didSelectItemAtIndexPath of a custom CollectionViewController which is a part of a tableViewCell

I want to instantiate a ViewController from a custom tableViewCell(conforming to UICollectionViewDelegate, UICollectionDatasource) class which has a custom CollectionView embedded in it.The segue needs to be performed when a particular section of the CollectionView is selected. Already tried using protocols doesnt work!
my custom TableView class :-
import UIKit
protocol transferDelegate{
func transfer(_: Int)
}
class ParentTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource{
#IBOutlet weak var parentCellHeader: UIView!
#IBOutlet weak var feedPostUsername: UILabel!
#IBOutlet weak var feedPostUserDetails: UILabel!
#IBOutlet weak var feedPostDescription: UILabel!
#IBOutlet weak var feedPicturesCollectionView: UICollectionView!
#IBOutlet weak var feedUserProfilePictures: CustomProfilepicture!
var transferingDelegate : transferDelegate?
override func awakeFromNib() {
super.awakeFromNib()
feedPicturesCollectionView.dataSource = self
feedPicturesCollectionView.delegate = self
feedPicturesCollectionView.pagingEnabled = true
feedPicturesCollectionView.backgroundColor = UIColor.clearColor()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) ->
UICollectionViewCell {
let cell = feedPicturesCollectionView.dequeueReusableCellWithReuseIdentifier("FeedPicturesCell", forIndexPath: indexPath) as! FeedPhotosCell
switch(indexPath.row){
case 0 : cell.feedImages.image = UIImage(named: "defaultProfilePic")
case 1 : cell.feedImages.image = UIImage(named: "image1")
case 2 : cell.feedImages.image = UIImage(named: "image2")
case 3 : cell.feedImages.image = UIImage(named: "image3")
default : cell.feedImages.image = UIImage(named: "defaultProfilePic")
}
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
print(indexPath.row)
transferingDelegate?.transfer(indexPath.row)
}
}
my viewController class : -
import UIKit
class ViewController: UIViewController , UITableViewDelegate, UITableViewDataSource,transferDelegate{
var xibName : String = "HomepageFeedCellHeader"
var lords : [String] = ["name1","name2","name3"]
var que : [String] = ["--","red","blue"]
var desc : [String] = ["abcE","defn","ghi"]
var menProfilePictures : [UIImage] = [UIImage(named: "image1")!,UIImage(named: "image2")!,UIImage(named: "image3")!]
#IBOutlet weak var parentTableView: UITableView!
var a : ParentTableViewCell = ParentTableViewCell()
override func viewDidLoad() {
super.viewDidLoad()
parentTableView.delegate = self
parentTableView.dataSource = self
// Do any additional setup after loading the view, typically from a nib.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lords.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = parentTableView.dequeueReusableCellWithIdentifier("ParentCell", forIndexPath: indexPath) as! ParentTableViewCell
a.transferingDelegate = self
cell.feedPostUsername.text = lords[indexPath.row]
cell.feedPostUserDetails.text = queens[indexPath.row]
cell.feedPostDescription.text = desc[indexPath.row]
cell.feedUserProfilePictures.image = menProfilePictures[indexPath.row]
return cell
}
func transfer(itemNo : Int) {
print("call recieved in viewController from item \(itemNo)")
}
}
Your didSelectItemAtIndexPath problem is solved. This is the answer of your second issue that you are mention in comment. You can not assign the image the imageView, if it is not in the navigation hierarchy. Your problem is that you detailImage is nil because this ViewController is not loaded in the window hierarchy. To solve your problem just do like this
imagePopOverScene?.selImage = menProfilePictures[itemNo]
Now declare selImage in your imagePopOverScene like this
var selImage: UIImage!
Also add follwing line in your viewDidLoad of imagePopOverSceneVC
self.detailImage.image = self.selImage
This will solve your problem

Resources