I currently always get an error when using UICollectionViewCells in a Storyboard. No other Controls show this behavior. How can I get rid of them?
This is how one of the affected CollectionViewCells looks like:
This is how I defined it:
Here is the code of the CategoryCollectionCell
import UIKit
import Foundation
#IBDesignable class CategoryCollectionCell : UICollectionViewCell {
#IBOutlet private weak var imageView: UIImageView!
#IBOutlet private weak var label: UILabel!
internal var id : Int?
override var highlighted : Bool {
didSet {
label.textColor = highlighted ? UIColor.greenColor() : UIColor.whiteColor()
}
}
#IBInspectable var text : String? {
get { return label.text }
set(value) { label.text = value }
}
#IBInspectable var image : UIImage? {
get { return imageView.image }
set(value) { imageView.image = value }
}
}
And this is the code of the CollectionViewController:
extension CategoryViewController : UICollectionViewController {
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = self.collectionView?.dequeueReusableCellWithReuseIdentifier(kReuseCellIdentifier, forIndexPath: indexPath)
var categoryCollectionCell = cell as? CategoryCollectionCell
if categoryCollectionCell == nil {
categoryCollectionCell = CategoryCollectionCell()
}
let data = getDataForIndexPath(indexPath)
if data != nil {
categoryCollectionCell?.id = data!.id
categoryCollectionCell!.text = data!.category
categoryCollectionCell!.image = data!.image
}
return categoryCollectionCell!
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 8
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
}
extension CategoryViewController : UICollectionViewDelegateFlowLayout {
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
guard let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout else {
return CGSize()
}
let width = CGRectGetWidth(collectionView.bounds)
let padding = flowLayout.sectionInset.left + flowLayout.sectionInset.right
let itemSpacing = flowLayout.minimumInteritemSpacing
let size = (width - padding - itemSpacing) / 2
return CGSize(width: size, height: size)
}
}
Ok. I found that the errors displayed by XCode had nothing to do with the actual problem.
The directory /Users/{Username}/Library/Logs/DiagnosticReports should contain files with names like this: IBDesignablesAgentCocoaTouch[...].crash
Inside them I found stacktraces which led me to the real problem:
The problem lied inside the code of a custom UITableViewCell instead of a UICollectionViewCell
class FooTableCell : UITableViewCell {
#IBOutlet private weak var checkmarkImageView: UIImageView!
override internal var selected : Bool {
didSet {
checkmarkImageView.hidden = !selected
}
}
}
The checkmarkImageView was nil when using the designer. Because of this the Cocoa Storyboard Agent crashed.
I fixed it by adding a guard statement:
class FooTableCell : UITableViewCell {
#IBOutlet private weak var checkmarkImageView: UIImageView!
override internal var selected : Bool {
didSet {
guard let imageView = checkmarkImageView else {
return
}
imageView.hidden = !selected
}
}
}
Related
I have a Collectionview in Tableviewcell. Using Alamofire and SwiftJSON i download data and store in my model project. I have 2 model file one for tableview, and one for collectionview
Model Project for Tableview:
class Matchs {
var matchTime : String
var matchMbs : Int
var matchLeague : String
var matchCode : Int
var matchHomeTeam : String
var matchAwayTeam : String
init(time : String, mbs : Int, league : String, code : Int, homeTeam : String, awayTeam : String ) {
matchTime = time
matchMbs = mbs
matchLeague = league
matchCode = code
matchHomeTeam = homeTeam
matchAwayTeam = awayTeam
}
}
Model Project for Collectionview:
class MatchsOdds {
var homeWin : Double
var awayWin : Double
var draw : Double
var under : Double
var over : Double
var oddResult = ["1","0","2","Under","Over"]
init(homeWin: Double, awayWin : Double, draw : Double, under : Double, over : Double) {
self.homeWin = homeWin
self.awayWin = awayWin
self.draw = draw
self.under = under
self.over = over
}
}
And here is the Viewcontroller code :
class MainViewController: UIViewController {
#IBOutlet weak var tableview: UITableView!
let MATCH_URL = "somelink"
var match : [Matchs] = []
var matchOdd : [MatchsOdds] = []
#IBOutlet weak var totalMatchLabel: UILabel!
#IBOutlet weak var totalOddsLabel: UILabel!
fileprivate func getMatchData(url:String) {
Alamofire.request(url).responseJSON {
response in
if response.result.isSuccess {
print("I got the match data")
let matchJSON : JSON = JSON(response.result.value!)
self.getMatch(json: matchJSON)
}else {
print("Match Unavailable")
}
self.tableview.reloadData()
}
}
fileprivate func getMatch(json: JSON) {
if let matchs = json["data"]["b"]["f"]["list"].array {
matchs.forEach { (arg0) in
let events = arg0
let tournamentName = events["tournamentName"].stringValue
let homeTeam = events["homeTeam"].stringValue
let mbs = events["mbs"].intValue
let matchCode = events["matchCode"].intValue
let matchTime : String = events["eventHour"].stringValue
let awayTeam = events["awayTeam"].stringValue
let matchTimeFirst5 = matchTime.prefix(5)
let allMatch = Matchs(time: String(matchTimeFirst5), mbs: mbs, league: tournamentName, code: matchCode, homeTeam: homeTeam, awayTeam: awayTeam)
let homeWin = events["oddList"][0]["v"].doubleValue
let draw = events["oddList"][1]["v"].doubleValue
let awaywin = events["oddList"][2]["v"].doubleValue
let under = events["oddList"][3]["v"].doubleValue
let over = events["oddList"][4]["v"].doubleValue
let fiveOdds = MatchsOdds(homeWin: homeWin, awayWin: awaywin, draw: draw, under: under, over: over)
match.append(allMatch)
matchOdd.append(fiveOdds)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
getMatchData(url: MATCH_URL)
}
override func viewDidAppear(_ animated: Bool) {
}
}
extension MainViewController: UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return match.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let matcCell = match[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "oddsCell") as! OddsTableViewCell
tableView.separatorStyle = .none
cell.matchToMatch(matcs: matcCell)
tableview.allowsSelection = false
cell.collectionview.tag = indexPath.row
cell.collectionview.reloadData()
cell.matchOddCV = matchOdd
return cell
}
}
No problem from here. I cat write data to Tableviewcell labels. But when i try the matchOdds data to Collectionview cell label. I only got first data which is repeating like this:
Here is the code for Tableviewcell :
class OddsTableViewCell: UITableViewCell {
#IBOutlet weak var reloadButton: RoundedButton!
#IBAction func reloadBottonPressed() {
print("reloadDataPressed")
}
var matchOddCV : [MatchsOdds]!
#IBOutlet weak var collectionview: UICollectionView!
#IBOutlet weak var timeLabel: UILabel!
#IBOutlet weak var mbsLabel: UILabel! {
didSet{
mbsLabel.layer.cornerRadius = 5
mbsLabel.layer.masksToBounds = true
}
}
#IBOutlet weak var leageLabel: UILabel!
#IBOutlet weak var matchCodeLabel: UILabel!
#IBOutlet weak var homeVsAwayLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
collectionview.dataSource = self
collectionview.delegate = self
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func matchToMatch (matcs : Matchs) {
timeLabel.text = matcs.matchTime
mbsLabel.text = String(matcs.matchMbs)
leageLabel.text = matcs.matchLeague
matchCodeLabel.text = String(matcs.matchCode)
homeVsAwayLabel.text = "\(matcs.matchHomeTeam) - \(matcs.matchAwayTeam)"
}
}
extension OddsTableViewCell: UICollectionViewDataSource, UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width/5, height: collectionView.bounds.height)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "oddsCollectionViewCell", for: indexPath) as! OddsCollectionViewCell
let matchCell = matchOddCV[indexPath.row]
if indexPath.row == 0 {
cell.matchResultLabel.text = matchCell.oddResult[0]
cell.matchOddsLabel.text = String(matchCell.homeWin)
}else if indexPath.row == 1{
cell.matchResultLabel.text = matchCell.oddResult[1]
cell.matchOddsLabel.text = String(matchCell.draw)
}else if indexPath.row == 2 {
cell.matchResultLabel.text = matchCell.oddResult[2]
cell.matchOddsLabel.text = String(matchCell.awayWin)
}else if indexPath.row == 3 {
cell.matchResultLabel.text = matchCell.oddResult[3]
cell.matchOddsLabel.text = String(matchCell.under)
}else if indexPath.row == 4 {
cell.matchResultLabel.text = matchCell.oddResult[4]
cell.matchOddsLabel.text = String(matchCell.over)
}
cell.matchOddsLabel.layer.borderWidth = 0.5
cell.matchOddsLabel.layer.borderColor = UIColor.lightGray.cgColor
cell.matchResultLabel.layer.borderWidth = 0.5
cell.matchResultLabel.layer.borderColor = UIColor.lightGray.cgColor
return cell
}
}
Collectionviewcell code :
class OddsCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var matchResultLabel: UILabel!
#IBOutlet weak var matchOddsLabel: UILabel!
}
According to the table's cellForRowAt
cell.collectionview.reloadData()
cell.matchOddCV = matchOdd
you assign the same array for all the collections inside all the table cells , what you need is to create an array property inside each Matchs object and do
let matcCell = match[indexPath.row]
cell.matchOddCV = matcCell.odds // create odds array and assign data for each object
cell.collectionview.reloadData()
As I can see, you are reloading the collectionview before setting the new list matchOddCV.
So you should do:
// assign the new list
cell.matchOddCV = matchOdd
// reload colllectionview
cell.collectionview.reloadData()
I created a custom UIView (DesignView) within a custom TableViewCell (TableViewCell) and I am trying to access the height property within the UIView so that I can grow it/shrink it when the TableViewCellexpands/shrinks.
I am running into the error of
cannot assign to property: 'height' is a get-only property
However after researching similar questions to mines none of the solutions/explanations seems to work.
The DesignView is within the TableViewCell and I am trying to accomplish my goal of making the DesignView "grow" when the cell expands in my main ViewController.
Here is my code for the custom UIView:
#IBDesignable class DesignView : UIView {
var newValue : CGFloat = 0
#IBInspectable var cornerRadius : CGFloat = 0
#IBInspectable var shadowColor : UIColor? = UIColor.black
#IBInspectable var shadowOffSetWidth : Int = 0
#IBInspectable var shadowOffSetHeight : Int = 1
#IBInspectable var shadowOpacity : Float = 0.2
override func layoutSubviews() {
layer.cornerRadius = cornerRadius/2
layer.shadowColor = shadowColor?.cgColor
layer.shadowOffset = CGSize(width: shadowOffSetWidth, height: shadowOffSetHeight)
let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
layer.shadowPath = shadowPath.cgPath
layer.shadowOpacity = shadowOpacity
}
}
Here is the code for my custom TableViewCell:
class TableViewCell: UITableViewCell {
#IBOutlet var nameLabel: UILabel!
#IBOutlet var phoneNumberLabel: UILabel!
#IBOutlet var addressLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Here is the code for my ViewController that contains the expand/shrink function I mentioned earlier:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var count : Int?
if tableView == self.tableView {
count = 3
} else if tableView == self.pantryTableView {
count = 7
} else if tableView == self.foodTableView {
count = 3
}
return count!
}
var selectedIndexPath : IndexPath? = nil
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("didSelectRowAtIndexPath was called")
var cell = tableView.cellForRow(at: indexPath) as! TableViewCell
switch selectedIndexPath {
case nil:
selectedIndexPath = indexPath
default:
if selectedIndexPath! == indexPath {
selectedIndexPath == nil
} else {
selectedIndexPath = indexPath
}
}
tableView.reloadRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath, _ sender: TableViewCell) -> CGFloat {
let smallHeight : CGFloat = 70
let expandedHeight : CGFloat = 180
let ip = indexPath
if selectedIndexPath != nil {
if ip == selectedIndexPath! {
return expandedHeight
} else {
return smallHeight
}
} else {
return smallHeight
}
}
Here is where the error is occurring in my TableViewCell:
class TableViewCell: UITableViewCell {
#IBOutlet var designView: DesignView!
var height : CGFloat {
get {
designView.bounds.height
}
set {
return designView.bounds.height = newValue
}
}
I'll also attach a screenshot so you can see what I'm trying to do/where the error is occurring:
let view = UIView(frame: .zero)
// You can only **access** the height as it is a get-only property
print(view.frame.height)
// You can **access** the height and **modify** it
view.frame.size.height = 50.0
You shouldn't need to increase the height if they are properly pinned to the edges of the Table view cell's content view.
In your code, designView.bounds returns a CGRect which is a struct. It doesn't have any property as height. Look the definition of CGRect below:
public struct CGRect {
public var origin: CGPoint
public var size: CGSize
public init()
public init(origin: CGPoint, size: CGSize)
}
Obviously, CGRect does not have any accessible property as height. This is the reason for which you are getting error.. You should get height from size property. There is also a return missing in get method. Replace lines of as below:
var height : CGFloat {
get {
return designView.bounds.size.height
}
set {
return designView.bounds.size.height = newValue
}
}
Hope, it will work!
I am trying to make a category list using CollectionView, as we can see there is a brown circle that actually an UIView (Designable Background View).
I want to update that background colour from brown to random colour. but the random colour only show up after I scroll up and scroll down the CollectionView, like the .gif file in here : http://g.recordit.co/BxRf26Uw2t.gif
before scroll down the colour will be still in brown like this
after scroll up and down, the colour will be updated, but some of the item still in the brown colour like this :
here is the code of my ViewController
import UIKit
import ChameleonFramework
class FacilitiesVC: UIViewController {
#IBOutlet weak var collectionVIew: UICollectionView!
struct StoryBoard {
// Collection View
static let indoorFacilitiesCategoryCellIdentifier = "indoorFacilitiesCell"
static let numberOfColumnsPerRow : CGFloat = 3.0
static let inset: CGFloat = 10.0
static let spacing: CGFloat = 8.0
static let lineSpacing: CGFloat = 8.0
//segue Identifiers
}
var indoorFacilitiesCategoryData = [FacilitiesCategory]()
var outdoorFacilitiesCategoryData = [FacilitiesCategory]()
override func viewDidLoad() {
super.viewDidLoad()
getIndoorOutdoorFacilitiesData()
}
}
extension FacilitiesVC {
func getIndoorOutdoorFacilitiesData() {
let facilitiesData = FacilitiesCategoryLibrary.fetchFacilitiesCategory()
// distinguishing between indoor and outdoor data
for facData in facilitiesData {
if facData.type == "Indoor Facility" {
indoorFacilitiesCategoryData.append(facData)
} else {
outdoorFacilitiesCategoryData.append(facData)
}
}
}
}
extension FacilitiesVC : UICollectionViewDataSource {
// MARK: - UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return indoorFacilitiesCategoryData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: StoryBoard.indoorFacilitiesCategoryCellIdentifier, for: indexPath) as! IndoorFacilitiesCell
cell.indoorFacilitiesCategoryData = indoorFacilitiesCategoryData[indexPath.item]
cell.designableBackgroundView.backgroundColor = RandomFlatColor()
cell.layer.borderColor = UIColor.lightGray.cgColor
cell.layer.borderWidth = 1
return cell
}
}
and here is the code of CollectionViewCell
import UIKit
import ChameleonFramework
class IndoorFacilitiesCell: UICollectionViewCell {
#IBOutlet weak var designableBackgroundView: DesignableUIView!
#IBOutlet weak var iconImageView: UIImageView!
#IBOutlet weak var icontitleLabel: UILabel!
var indoorFacilitiesCategoryData : FacilitiesCategory? {
didSet {
updateUI()
}
}
}
extension IndoorFacilitiesCell {
func updateUI() {
guard let indoorData = indoorFacilitiesCategoryData else {return}
iconImageView.image = UIImage(named: indoorData.logo)
icontitleLabel.text = indoorData.categoryName
}
}
what went wrong in here? what should I do to fix that?
For some strange reason the collectionView isn't called. I put break points at numberOfItemsInSection and cellForItemAtIndexPath but they are never called. Here is my code:
class ChannelViewController: UIViewController, UISearchResultsUpdating, UICollectionViewDataSource, UICollectionViewDelegate {
var channelArray: [ChannelInfo] = []
var filteredSearchResults = [ChannelInfo]()
var resultsSearchController = UISearchController(searchResultsController: nil)
var logosShown = [Bool](count: 50, repeatedValue: false)
var detailUrl: String?
var apiKey = ""
var channel: String!
var channelForShow: String!
var task: NSURLSessionTask?
#IBOutlet var channelCollectionView: UICollectionView!
override func viewDidLoad() {
let baseURL = ""
getJSON(baseURL)
}
//MARK: CollectionView
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if resultsSearchController.active && resultsSearchController.searchBar.text != ""
{
return filteredSearchResults.count
} else {
return channelArray.count
}
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ChannelCell", forIndexPath: indexPath) as! ChannelCell
let channel: String
if resultsSearchController.active && resultsSearchController.searchBar.text != "" {
channel = self.filteredSearchResults[indexPath.row].logo
} else {
channel = self.channelArray[indexPath.row].logo
}
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
var replacedTitle: String?
if resultsSearchController.active && resultsSearchController.searchBar.text != "" {
channelForShow = filteredSearchResults[indexPath.row].channelName
}
} else {
channelForShow = self.channelArray[indexPath.row].channelName
}
}
self.dismissSearchBar()
performSegueWithIdentifier("channelCollectToShowSegue", sender: self)
}
}
I also have a cell subclassed for the collectionView Cell:
class ChannelCell : UICollectionViewCell {
#IBOutlet var channelImageView: UIImageView!
}
You need to set the datasource of collection view to the controller.
1) In Interface builder, just drag from the collection view to the controller. and choose both the delegate and datasource..
2) Programatically
override func viewDidLoad() {
super.viewDidLoad()
channelCollectionView.delegate = self
channelCollectionView.dataSource = self
let baseURL = ""
getJSON(baseURL)
}
Did you set the collectionView's delegate and dataSource properties? If not, change your viewDidLoad to this, in order to set those two properties programmatically:
override func viewDidLoad() {
channelCollectionView.delegate = self
channelCollectionView.dataSource = self
let baseURL = ""
getJSON(baseURL)
}
If you don't set the delegate and dataSource, the collectionView won't know where to look to call the appropriate methods, such as cellForItemAtIndexPath. Hopefully this fixes your problem.
EDIT: seems like same question as this
I made collection view with custom layout and cell but my cell's positions were a little off so I decided to number them so that I could debug it easier. I followed this guy's answer to number my cell using labels. Somehow myLabel was nil and caused my app to crash.
I believe I have connected the outlets correctly but maybe someone could provide a list for me to check if I am doing anything wrong.
ViewController
import UIKit
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet var gridView: UICollectionView!
private let reuseIdentifier = "DesignCell"
private let numberOfSections = 1
private let numberOfCircles = 48
override func viewDidLoad() {
super.viewDidLoad()
self.gridView.registerClass(MyCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return numberOfSections
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return numberOfCircles
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! MyCell
cell.myLabel.text = String(indexPath.item) // myLabel is nil and causes a crash
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
GridLayout (my custom layout)
import UIKit
import Darwin
class GridLayout: UICollectionViewLayout {
private let numberOfColumns = 12
private let numberOfRows = 4
private var cache = [UICollectionViewLayoutAttributes]()
private var contentHeight: CGFloat = 0
private var contentWidth: CGFloat {
let insets = collectionView!.contentInset
return CGRectGetWidth(collectionView!.bounds) - insets.left - insets.right
}
override func prepareLayout() {
// some calculation i did for my cells
}
override func collectionViewContentSize() -> CGSize {
return CGSize(width: contentWidth, height: contentHeight)
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
for attr in cache {
if CGRectIntersectsRect(attr.frame, rect) {
layoutAttributes.append(attr)
}
}
return layoutAttributes
}
}
MyCell
import UIKit
class MyCell : UICollectionViewCell {
#IBOutlet weak var myLabel: UILabel!
}
Try to remove this line within viewDidLoad() in your ViewController class:
self.gridView.registerClass(MyCell.self, forCellWithReuseIdentifier: reuseIdentifier)
It's not needed since you have created your UICollectionView in Storyboard, connected to your dataSource and delegate, and you have added all the required methods:
numberOfItemsInSection
cellForItemAtIndexPath