collection view presentation of graph: "no chart data available" for Charts cocoapods - ios

I'm coding a GraphViewController class that houses an array of graphs (of type LineChartView). However, when I attempt to display these graphs in the format of cells of a collection view (using the called class GraphCell), the LineChartView objects don't seem to load any data, even though these functions are called inside the GraphViewController class. Here are the relevant bits of my code so far:
class GraphViewController: UIViewController {
//lazy var only calculated when called
lazy var lineChartView: LineChartView = {
let chartView = LineChartView()
chartView.backgroundColor = .systemBlue
chartView.rightAxis.enabled = false //right axis contributes nothing
let yAxis = chartView.leftAxis
yAxis.labelFont = .boldSystemFont(ofSize: 12)
yAxis.setLabelCount(6, force: false)
yAxis.labelTextColor = .white
yAxis.axisLineColor = .white
yAxis.labelPosition = .outsideChart
let xAxis = chartView.xAxis
xAxis.labelPosition = .bottom
xAxis.labelFont = .boldSystemFont(ofSize: 12)
xAxis.setLabelCount(6, force: false)
xAxis.labelTextColor = .white
xAxis.axisLineColor = .systemBlue
chartView.animate(xAxisDuration: 1)
return chartView
}()
var graphColl: UICollectionView!
var graphs: [LineChartView] = []
var graphReuseID = "graph"
var homeViewController: ViewController = ViewController()
let dataPts = 50
var yValues: [ChartDataEntry] = []
var allWordsNoPins: [Word] = []
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setUpViews();
setUpConstraints()
}
func setUpViews(){
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = padding/3.2
layout.scrollDirection = .vertical
graphColl = UICollectionView(frame: .zero, collectionViewLayout: layout)
graphColl.translatesAutoresizingMaskIntoConstraints = false
graphColl.dataSource = self
graphColl.delegate = self
//must register GraphCell class before calling dequeueReusableCell
graphColl.register(GraphCell.self, forCellWithReuseIdentifier: graphReuseID)
graphColl.backgroundColor = .white
view.addSubview(graphColl)
print("stuff assigned")
assignData()
setData()
graphs.append(lineChartView)
}
One can assume setUpConstraints() is working correctly, as the graph collection does show up. Here are all the functions that have to deal with the collection view I'm using:
//INSIDE GraphViewController
extension GraphViewController: UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
//collectionView.frame.height -
let size = 25*padding
let sizeWidth = collectionView.frame.width - padding/3
return CGSize(width: sizeWidth, height: size)
}
}
extension GraphViewController: UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return graphs.count //total number of entries
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let graphColl = collectionView.dequeueReusableCell(withReuseIdentifier: graphReuseID, for: indexPath) as! GraphCell
graphColl.configure(graph: graphs[indexPath.item])
return graphColl
}
}
and:
//configure function INSIDE GraphCell
func configure(graph: LineChartView){
chartView = graph
}
Here are the assignData() and setData() functions:
func setData(){
let set1 = LineChartDataSet(entries: yValues, label: "Word Frequency")
let data = LineChartData(dataSet: set1)
set1.drawCirclesEnabled = true
set1.circleRadius = 3
set1.mode = .cubicBezier //smoothes out curve
set1.setColor(.white)
set1.lineWidth = 3
set1.drawHorizontalHighlightIndicatorEnabled = false //ugly yellow line
data.setDrawValues(false)
lineChartView.data = data
}
func assignData(){
setUpTempWords()
let dataValues = allWordsNoPins
print(allWordsNoPins.count)
for i in 0...dataPts-1{
yValues.append(ChartDataEntry(x: Double(i), y: Double(dataValues[i].count)))
}
}
One can also assume the setUpTempWords() function is working correctly, because of this screenshot below:
Here, I have plotted the lineChartView object of type LineChartView directly on top of the GraphColl UICollectionView variable inside my GraphViewController class. The data is displayed. However, when I try to plot the same graph in my GraphCell class, I get
"No chart data available." I have traced the calls in my GraphViewController class, and based on the way the viewDidLoad() function is set up, I can conclude that the lineChartView setup methods (assignData(), etc) are being called. For reference, here is my GraphCell class code:
import UIKit
import Charts
class GraphCell: UICollectionViewCell {
var chartView = LineChartView()
var graphCellBox: UIView!
override init(frame: CGRect){
super.init(frame: frame)
contentView.backgroundColor = .blue
chartView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(chartView)
graphCellBox = UIView()
graphCellBox.translatesAutoresizingMaskIntoConstraints = false
//graphCellBox.backgroundColor = cellorange
graphCellBox.layer.cornerRadius = 15.0
contentView.addSubview(graphCellBox)
setUpConstraints()
}
func setUpConstraints(){
NSLayoutConstraint.activate([
graphCellBox.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5),
graphCellBox.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
graphCellBox.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
graphCellBox.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
chartView.topAnchor.constraint(equalTo: graphCellBox.topAnchor),
chartView.bottomAnchor.constraint(equalTo: graphCellBox.bottomAnchor),
chartView.leadingAnchor.constraint(equalTo: graphCellBox.leadingAnchor),
chartView.trailingAnchor.constraint(equalTo: graphCellBox.trailingAnchor),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(graph: LineChartView){
chartView = graph
}
}
As a side note, changing the lineChartView to a non-lazy (normal) variable does not fix this problem. I suspected the lazy declaration was the problem, since the graph would only be initialize if called, but this was not the case. Thank you for reading this post, and I'd greatly appreciate any direction or guidance!

Tough to test this without a reproducible example, but...
Assigning chartView = graph looks problematic.
Try using your graphCellBoxgraphCellBox as a "container" for the LineChartView you're passing in with configure(...):
class GraphCell: UICollectionViewCell {
var graphCellBox: UIView!
override init(frame: CGRect){
super.init(frame: frame)
contentView.backgroundColor = .blue
graphCellBox = UIView()
graphCellBox.translatesAutoresizingMaskIntoConstraints = false
//graphCellBox.backgroundColor = cellorange
graphCellBox.layer.cornerRadius = 15.0
contentView.addSubview(graphCellBox)
setUpConstraints()
}
func setUpConstraints(){
NSLayoutConstraint.activate([
graphCellBox.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5),
graphCellBox.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
graphCellBox.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
graphCellBox.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(graph: LineChartView){
graph.translatesAutoresizingMaskIntoConstraints = false
graphCellBox.addSubview(graph)
NSLayoutConstraint.activate([
graph.topAnchor.constraint(equalTo: graphCellBox.topAnchor),
graph.bottomAnchor.constraint(equalTo: graphCellBox.bottomAnchor),
graph.leadingAnchor.constraint(equalTo: graphCellBox.leadingAnchor),
graph.trailingAnchor.constraint(equalTo: graphCellBox.trailingAnchor),
])
}
}

Related

How to change collectionview cells color based on device theme (following my color scheme)

Overview:
I'm building a keyboard Extension using collectionviews. I want the cells to change color based on the device theme (light/dark). At the moment, when I set the color scheme for my collectionview cells they don't work. I'm marking the problematic parts of my code with a "///" comment.
Resources:
I found this RayWenderlich project and I liked how they handled the color changing stuff so I copied it.
My code:
I have 3 classes:
KeyboardViewController
Custom View containing keyboard buttons
Custom collectionview cells
CollectionView cell
class KeyboardKeys: UICollectionViewCell {
var defaultColor = UIColor.white
var highlighColor = UIColor.lightGray.withAlphaComponent(0.6)
let label: UILabel = {
let iv = UILabel()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFit
iv.font = UIFont.systemFont(ofSize: 20)
iv.clipsToBounds = true
iv.numberOfLines = 1
iv.textAlignment = .center
return iv
}()
override init(frame: CGRect) {
super.init(frame: .zero)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
contentView.addSubview(label)
label.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
label.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
label.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
override func layoutSubviews() {
super.layoutSubviews()
backgroundColor = isHighlighted ? highlighColor : defaultColor
}
}
Custom View
class lettersKeyboard: UIView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
var keyView: UICollectionView!
let letters = ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"]
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit(){
//If you find some errors it's because this is way different in my code. This is just a regulare collection view anyway
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
keyView = UICollectionView(frame: CGRect(x: 0.0, y: 0.0 , width: frame.width, height: 280), collectionViewLayout: layout)
keyView.setCollectionViewLayout(layout, animated: true)
keyView.isScrollEnabled = false
keyView.register(KeyboardKeys.self, forCellWithReuseIdentifier: "collectionCellId")
keyView.delegate = self
keyView.dataSource = self
addSubview(keyView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = keyView.dequeueReusableCell(withReuseIdentifier: "collectionCellId", for: indexPath) as! KeyboardKeys
cell.label.text = letters[indexPath.row]
return cell
}
///I guess something is wrong here
func setColorScheme(_ colorScheme: ColorScheme) {
let colorScheme = CColors(colorScheme: colorScheme)
for view in subviews {
if let cell = view as? KeyboardKeys {
cell.tintColor = colorScheme.buttonTextColor
cell.defaultColor = colorScheme.keysDefaultColor
cell.highlighColor = colorScheme.keysHighlightColor
}
}
}
}
Color scheme struct
enum ColorScheme {
case dark
case light
}
struct CColors {
let keysDefaultColor: UIColor
let keysHighlightColor: UIColor
let buttonTextColor: UIColor
init(colorScheme: ColorScheme) {
switch colorScheme {
case .light:
keysDefaultColor = .systemRed
//UIColor.white
keysHighlightColor = UIColor.lightGray.withAlphaComponent(0.6)
buttonTextColor = .black
case .dark:
keysDefaultColor = .systemBlue
// UIColor.gray.withAlphaComponent(0.5)
keysHighlightColor = UIColor.lightGray.withAlphaComponent(0.5)
buttonTextColor = .white
}
}
}
KeyboardViewController
class KeyboardViewController: UIInputViewController {
var letters : lettersKeyboard = {
let m = lettersKeyboard(frame: .zero)
m.translatesAutoresizingMaskIntoConstraints = false
m.backgroundColor = .clear
return m
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(letters)
letters.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
letters.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
letters.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
letters.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
//The rest is the default inputvc stuff
///Or here
override func textDidChange(_ textInput: UITextInput?) {
// The app has just changed the document's contents, the document context has been updated.
let colorScheme: ColorScheme
let proxy = self.textDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.dark {
colorScheme = .dark
} else {
colorScheme = .light
}
letters.setColorScheme(colorScheme)
}
}
Question:
I don't know what I'm doing wrong since my code works with everything except for collectionview cells. I guess another way of doing this stuff exists. So how do I change my collectionView cells' color based on the device's theme following my color scheme?
You should really be reloading the collection view, rather than trying to find the subviews that are the keys, and updating those.
Pass in the colorScheme model to each cell and have the colors be set as a result of a reload.
A very kind guy helped me out and found this solution. The problem here is that I forgot the view's hierarchy.
CollectionView cell
override func layoutSubviews() {
super.layoutSubviews()
setupBackGround()
}
func setupBackGround(){
backgroundColor = isHighlighted ? highlighColor : defaultColor
}
KeyboardViewController
func setColorScheme(_ colorScheme: ColorScheme) {
let colorScheme = CColors(colorScheme: colorScheme)
for view in subviews {
func setToRootView(view: UIView) {
if let cell = view as? KeyboardKeys {
cell.tintColor = colorScheme.buttonTextColor
cell.defaultColor = colorScheme.keysDefaultColor
cell.highlighColor = colorScheme.keysHighlightColor
cell.setBackground()
return
}
guard view.subviews.count > 0 else {
return
}
view.subviews.forEach(setToRootView(view:))
}
setToRootView(view: self)
}

UIScrollView not showing up in the view

I am implementing a UIScrollView in a CollectionViewCell. I have a custom view which the scroll view should display, hence I am performing the following program in the CollectionViewCell. I have created everything programmatically and below is my code :
struct ShotsCollections {
let title: String?
}
class ShotsMainView: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
containerScrollView.contentSize.width = frame.width * CGFloat(shotsData.count)
shotsData = [ShotsCollections.init(title: "squad"), ShotsCollections.init(title: "genral")]
var i = 0
for data in shotsData {
let customview = ShotsMediaView(frame: CGRect(x: containerScrollView.frame.width * CGFloat(i), y: 0, width: containerScrollView.frame.width, height: containerScrollView.frame.height))
containerScrollView.addSubview(customview)
i += 1
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var shotsData = [ShotsCollections]()
var containerScrollView: UIScrollView = {
let instance = UIScrollView()
instance.isScrollEnabled = true
instance.bounces = true
instance.backgroundColor = blueColor
return instance
}()
private func setupViews() { //These are constraints by using TinyConstraints
addSubview(containerScrollView)
containerScrollView.topToSuperview()
containerScrollView.bottomToSuperview()
containerScrollView.rightToSuperview()
containerScrollView.leftToSuperview()
}
}
Now the issue is, while the scrollview is displayed, the content in it is not. I on printing the contentSize and frame of the scrollview, it displays 0. But if I check the Debug View Hierarchy, scrollview containes 2 views with specific frames.
I am not sure whats going wrongs. Any help is appreciated.
When you are adding customView in your containerScrollView, you are not setting up the constraints between customView and containerScrollView.
Add those constraints and you will be able to see your customViews given that your customView has some height. Also, when you add more view, you would need to remove the bottom constraint of the last added view and create a bottom constraint to the containerScrollView with the latest added view.
I created a sample app for your use case. I am pasting the code and the resultant screen shot below. Hope this is the functionality you are looking for. I suggest you paste this in a new project and tweak the code until you are satisfied. I have added comments to make it clear.
ViewController
import UIKit
class ViewController: UIViewController {
// Initialize dummy data array with numbers 0 to 9
var data: [Int] = Array(0..<10)
override func loadView() {
super.loadView()
// Add collection view programmatically
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(ShotsMainView.self, forCellWithReuseIdentifier: ShotsMainView.identifier)
self.view.addSubview(collectionView)
NSLayoutConstraint.activate([
self.view.topAnchor.constraint(equalTo: collectionView.topAnchor),
self.view.bottomAnchor.constraint(equalTo: collectionView.bottomAnchor),
self.view.leadingAnchor.constraint(equalTo: collectionView.leadingAnchor),
self.view.trailingAnchor.constraint(equalTo: collectionView.trailingAnchor),
])
collectionView.delegate = self
collectionView.dataSource = self
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = UIColor.white
self.view.addSubview(collectionView)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = UIColor.white
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ShotsMainView.identifier, for: indexPath) as! ShotsMainView
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// The cell dimensions are set from here
return CGSize(width: collectionView.frame.size.width, height: 100.0)
}
}
ShotsMainView
This is the collection view cell
import UIKit
class ShotsMainView: UICollectionViewCell {
static var identifier = "Cell"
weak var textLabel: UILabel!
override init(frame: CGRect) {
// Initialize with zero frame
super.init(frame: frame)
// Add the scrollview and the corresponding constraints
let containerScrollView = UIScrollView(frame: .zero)
containerScrollView.isScrollEnabled = true
containerScrollView.bounces = true
containerScrollView.backgroundColor = UIColor.blue
containerScrollView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(containerScrollView)
NSLayoutConstraint.activate([
self.topAnchor.constraint(equalTo: containerScrollView.topAnchor),
self.bottomAnchor.constraint(equalTo: containerScrollView.bottomAnchor),
self.leadingAnchor.constraint(equalTo: containerScrollView.leadingAnchor),
self.trailingAnchor.constraint(equalTo: containerScrollView.trailingAnchor)
])
// Add the stack view that will hold the individual items that
// in each row that need to be scrolled horrizontally
let stackView = UIStackView(frame: .zero)
stackView.distribution = .fill
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
containerScrollView.addSubview(stackView)
stackView.backgroundColor = UIColor.magenta
NSLayoutConstraint.activate([
containerScrollView.leadingAnchor.constraint(equalTo: stackView.leadingAnchor),
containerScrollView.trailingAnchor.constraint(equalTo: stackView.trailingAnchor),
containerScrollView.topAnchor.constraint(equalTo: stackView.topAnchor),
containerScrollView.bottomAnchor.constraint(equalTo: stackView.bottomAnchor)
])
// Add individual items (Labels in this case).
for i in 0..<10 {
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(label)
label.text = "\(i)"
label.font = UIFont(name: "System", size: 20.0)
label.textColor = UIColor.white
label.backgroundColor = UIColor.purple
label.layer.masksToBounds = false
label.layer.borderColor = UIColor.white.cgColor
label.layer.borderWidth = 1.0
label.textAlignment = .center
NSLayoutConstraint.activate([
label.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 1.0, constant: 0.0),
label.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.2, constant: 0.0)
])
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Screenshot

Collection View as Custom Keyboard not working

I am building application where you have custom keyboard.
Inside it's class I have created collection view, here is code:
class KeyboardViewController: UIInputViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate {
let stickerImages = [
UIImage(named: "Image-1"),
UIImage(named: "Image-2"),
UIImage(named: "Image-3"),
UIImage(named: "Image-4"),
UIImage(named: "Image-5")
]
#IBOutlet var nextKeyboardButton: UIButton!
#IBOutlet var collectionView: UICollectionView!
override func updateViewConstraints() {
super.updateViewConstraints()
}
override func viewDidLoad() {
super.viewDidLoad()
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.scrollDirection = UICollectionView.ScrollDirection.vertical
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: 50, height: 50)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(StickersCell.self, forCellWithReuseIdentifier: StickersCell.reuseIdentifier)
collectionView.backgroundColor = UIColor.white
collectionView.showsHorizontalScrollIndicator = false
collectionView.backgroundColor = UIColor.red
collectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(collectionView)
self.nextKeyboardButton = UIButton(type: .system)
self.nextKeyboardButton.setTitle(NSLocalizedString("Next Keyboard", comment: "Title for 'Next Keyboard' button"), for: [])
self.nextKeyboardButton.sizeToFit()
self.nextKeyboardButton.translatesAutoresizingMaskIntoConstraints = false
self.nextKeyboardButton.backgroundColor = UIColor.white
self.nextKeyboardButton.addTarget(self, action: #selector(handleInputModeList(from:with:)), for: .allTouchEvents)
self.view.addSubview(self.nextKeyboardButton)
self.nextKeyboardButton.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.nextKeyboardButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.collectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.collectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
}
override func textWillChange(_ textInput: UITextInput?) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(_ textInput: UITextInput?) {
// The app has just changed the document's contents, the document context has been updated.
var textColor: UIColor
let proxy = self.textDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.dark {
textColor = UIColor.white
} else {
textColor = UIColor.black
}
self.nextKeyboardButton.setTitleColor(textColor, for: [])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return stickerImages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: StickersCell.reuseIdentifier, for: indexPath) as! StickersCell
cell.setImage(stickerImages[indexPath.item]!)
return cell
}}
And here is my Collection View cell class:
class StickersCell: UICollectionViewCell {
static let reuseIdentifier: String = "StickersCell"
lazy var imageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.contentMode = .scaleAspectFit
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.clipsToBounds = true
contentView.addSubview(imageView)
imageView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
imageView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setImage(_ image: UIImage) {
imageView.image = image
}}
This code works just fine when adding collection view to any UIView or UIViewController, but when trying to add it to keyboard it throws such errors:
As far as I understand I have placed constraints wrong, but I don't understand what exactly is wrong, especially when it's working fine in simple views or view controllers.
I have googled allot and couldn't find any solutions...
Also this SO questions didn't help as well:
First question
Second question
Third question
I have also tried moving code that creates collection view into viewDidAppear and viewWillAppear methods but same no luck.
Another strange thing:
If I add simple UIView with same constraints to keyboard - everything is working fine. Problem seems to be specifically with collection view.
So, what I have missed? Would be grateful for any help, since I'm battling with this issue for over a week already...
UPDATE:
After reading Apple dev forums, idea came up to my mind:
I have created UITableView same as UICollectionView before and strangely it works. So there's next question:
Are you able to use UICollectionView as custom keyboard at all?
After battling with this issue for 2 weeks finally found working workaround:
For some reason you can't use UIImageView or MSStickerView inside UICollectionViewCell same as in iMessage Extension, so instead I just added transparent UIButton with UIImage inside this button and it worked!
Still don't know why you can't use images or views and couldn't find any specific info about it, but my solutions works and I hope this will help someone in future.

Delegate Function Not Being Called

So I am trying to use protocols and delegates to connect two functions so I can perform some operation on a variable a collectionView in this case in a different file.
import Foundation
import UIKit
protocol EventCollectionCellDelegate: NSObjectProtocol {
func setupCollectionView(for eventCollectionView: UICollectionView?)
}
class EventCollectionCell:UICollectionViewCell {
weak var delegate: EventCollectionCellDelegate?
var eventArray = [EventDetails](){
didSet{
self.eventCollectionView.reloadData()
}
}
var enentDetails:Friend?{
didSet{
var name = "N/A"
var total = 0
seperator.isHidden = true
if let value = enentDetails?.friendName{
name = value
}
if let value = enentDetails?.events{
total = value.count
self.eventArray = value
seperator.isHidden = false
}
if let value = enentDetails?.imageUrl{
profileImageView.loadImage(urlString: value)
}else{
profileImageView.image = #imageLiteral(resourceName: "Tokyo")
}
self.eventCollectionView.reloadData()
setLabel(name: name, totalEvents: total)
}
}
let container:UIView={
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 16
view.layer.borderColor = UIColor.lightGray.cgColor
view.layer.borderWidth = 0.3
return view
}()
//profile image view for the user
var profileImageView:CustomImageView={
let iv = CustomImageView()
iv.layer.masksToBounds = true
iv.layer.borderColor = UIColor.lightGray.cgColor
iv.layer.borderWidth = 0.3
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
//will show the name of the user as well as the total number of events he is attending
let labelNameAndTotalEvents:UILabel={
let label = UILabel()
label.textColor = .black
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
return label
}()
let seperator:UIView={
let view = UIView()
view.backgroundColor = .lightGray
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
//collectionview that contains all of the events a specific user will be attensing
let flow = UICollectionViewFlowLayout()
lazy var eventCollectionView = UICollectionView(frame: .zero, collectionViewLayout: flow)
// var eventCollectionView:UICollectionView?
override init(frame: CGRect) {
super.init(frame: frame)
self.setUpCell()
self.setupCollectionView(for: eventCollectionView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupCollectionView(for eventCollectionView: UICollectionView?){
delegate?.setupCollectionView(for: eventCollectionView)
}
}
This is the file that creates a collectionViewCell with a collectionView in it. I am trying to perform some operation on that collectionView using the delegate pattern. My problem is that the delegate function is never called in the accompanying viewController. I feel like I have done everything right but nothing happens in the accompanying vc. Anyone notice what could possibly be wrong.
I have shown the code for the VC below
class FriendsEventsView: UIViewController,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout,EventCollectionCellDelegate {
var friends = [Friend]()
var followingUsers = [String]()
var height:CGFloat = 0
var notExpandedHeight : CGFloat = 50
var isExpanded = [Bool]()
//so this is the main collectonview that encompasses the entire view
lazy var mainCollectionView:UICollectionView={
// the flow layout which is needed when you create any collection view
let flow = UICollectionViewFlowLayout()
//setting the scroll direction
flow.scrollDirection = .vertical
//setting space between elements
let spacingbw:CGFloat = 5
flow.minimumLineSpacing = spacingbw
flow.minimumInteritemSpacing = 0
//actually creating collectionview
let cv = UICollectionView(frame: .zero, collectionViewLayout: flow)
//register a cell for that collectionview
cv.register(EventCollectionCell.self, forCellWithReuseIdentifier: "events")
cv.translatesAutoresizingMaskIntoConstraints = false
//changing background color
cv.backgroundColor = .white
//sets the delegate of the collectionView to self. By doing this all messages in regards to the collectionView will be sent to the collectionView or you.
//"Delegates send messages"
cv.delegate = self
//sets the datsource of the collectionView to you so you can control where the data gets pulled from
cv.dataSource = self
//sets positon of collectionview in regards to the regular view
cv.contentInset = UIEdgeInsetsMake(spacingbw, 0, spacingbw, 0)
return cv
}()
lazy var eventCollectionView:UICollectionView={
let flow = UICollectionViewFlowLayout()
flow.scrollDirection = .vertical
let spacingbw:CGFloat = 5
flow.minimumLineSpacing = 0
flow.minimumInteritemSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: flow)
//will register the eventdetailcell
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .white
cv.register(EventDetailsCell.self, forCellWithReuseIdentifier: "eventDetails")
cv.delegate = self
cv.dataSource = self
cv.contentInset = UIEdgeInsetsMake(spacingbw, 0, spacingbw, 0)
cv.showsVerticalScrollIndicator = false
cv.bounces = false
return cv
}()
func setupCollectionView(for eventCollectionView: UICollectionView?) {
print("Attempting to create collectonView")
eventCollectionView?.backgroundColor = .blue
}
//label that will be displayed if there are no events
let labelNotEvents:UILabel={
let label = UILabel()
label.textColor = .lightGray
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = UIFont.italicSystemFont(ofSize: 14)
label.text = "No events found"
label.isHidden = true
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
//will set up all the views in the screen
self.setUpViews()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "close_black").withRenderingMode(.alwaysOriginal), style: .done, target: self, action: #selector(self.goBack))
}
func setUpViews(){
//well set the navbar title to Friends Events
self.title = "Friends Events"
view.backgroundColor = .white
//adds the main collection view to the view and adds proper constraints for positioning
view.addSubview(mainCollectionView)
mainCollectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
mainCollectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
mainCollectionView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
mainCollectionView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
//adds the label to alert someone that there are no events to the collectionview and adds proper constrains for positioning
mainCollectionView.addSubview(labelNotEvents)
labelNotEvents.centerYAnchor.constraint(equalTo: mainCollectionView.centerYAnchor, constant: 0).isActive = true
labelNotEvents.centerXAnchor.constraint(equalTo: mainCollectionView.centerXAnchor, constant: 0).isActive = true
//will fetch events from server
self.fetchEventsFromServer()
}
// MARK: CollectionView Datasource for maincollection view
//woll let us know how many cells are being displayed
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(friends.count)
isExpanded = Array(repeating: false, count: friends.count)
return friends.count
}
//will control the size of the cell that is displayed in the containerview
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
height = 100
let event = friends[indexPath.item]
if let count = event.events?.count,count != 0{
height += (CGFloat(count*40)+10)
}
return CGSize(width: collectionView.frame.width, height: height)
}
//will do the job of effieicently creating cells for the eventcollectioncell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "events", for: indexPath) as! EventCollectionCell
cell.delegate = self
cell.enentDetails = friends[indexPath.item]
cell.eventCollectionView = eventCollectionView
return cell
}
}
I have cut the code down to what I believe is needed to answer the question for simplicity. Any help is appreciated
You set delegate after setupCollectionView. In your case, you can't call setupCollectionView before you set delegate, because setupCollectionView called in init

How can I change my UICollectionView's Flow Layout to a vertical List with Horizontal Scrolling

Basically what I am trying to create is a table with three cells stacked on top of one another. But, if there are more than three cells, I want to be able to swipe left on the Collection View to show more cells. Here is a picture to illustrate.
Right now I have the cells arranged in a list but I cannot seem to change the scroll direction for some reason. - They still scroll vertically
Here is my current code for the Flow Layout:
Note: I'm not going to include the Collection View code that is in my view controller as I do not think it is relevant.
import Foundation
import UIKit
class HorizontalListCollectionViewFlowLayout: UICollectionViewFlowLayout {
let itemHeight: CGFloat = 35
func itemWidth() -> CGFloat {
return collectionView!.frame.width
}
override var itemSize: CGSize {
set {
self.itemSize = CGSize(width: itemWidth(), height: itemHeight)
}
get {
return CGSize(width: itemWidth(), height: itemHeight)
}
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
return collectionView!.contentOffset
}
override var scrollDirection: UICollectionViewScrollDirection {
set {
self.scrollDirection = .horizontal
} get {
return self.scrollDirection
}
}
}
If you have your cells sized correctly, Horizontal Flow Layout will do exactly what you want... fill down and across.
Here is a simple example (just set a view controller to this class - no IBOutlets needed):
//
// ThreeRowCViewViewController.swift
//
// Created by Don Mag on 6/20/17.
//
import UIKit
private let reuseIdentifier = "LabelItemCell"
class LabelItemCell: UICollectionViewCell {
// simple CollectionViewCell with a label
#IBOutlet weak var theLabel: UILabel!
let testLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14)
label.textColor = UIColor.black
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addViews()
}
func addViews(){
addSubview(testLabel)
testLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8.0).isActive = true
testLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0.0).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ThreeRowCViewViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
// 3 gray colors for the rows
let cellColors = [
UIColor.init(white: 0.9, alpha: 1.0),
UIColor.init(white: 0.8, alpha: 1.0),
UIColor.init(white: 0.7, alpha: 1.0)
]
var theCodeCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
// height we'll use for the rows
let rowHeight = 30
// just picked a random width of 240
let rowWidth = 240
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
// horizontal collection view direction
layout.scrollDirection = .horizontal
// each cell will be the width of the collection view and our pre-defined height
layout.itemSize = CGSize(width: rowWidth - 1, height: rowHeight)
// no item spacing
layout.minimumInteritemSpacing = 0.0
// 1-pt line spacing so we have a visual "edge" (with horizontal layout, the "lines" are vertical blocks of cells
layout.minimumLineSpacing = 1.0
theCodeCollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
theCodeCollectionView.dataSource = self
theCodeCollectionView.delegate = self
theCodeCollectionView.register(LabelItemCell.self, forCellWithReuseIdentifier: reuseIdentifier)
theCodeCollectionView.showsVerticalScrollIndicator = false
// set background to orange, just to make it obvious
theCodeCollectionView.backgroundColor = .orange
theCodeCollectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(theCodeCollectionView)
// set collection view width x height to rowWidth x (rowHeight * 3)
theCodeCollectionView.widthAnchor.constraint(equalToConstant: CGFloat(rowWidth)).isActive = true
theCodeCollectionView.heightAnchor.constraint(equalToConstant: CGFloat(rowHeight * 3)).isActive = true
// center the collection view
theCodeCollectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0).isActive = true
theCodeCollectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0).isActive = true
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! LabelItemCell
cell.backgroundColor = cellColors[indexPath.row % 3]
cell.testLabel.text = "\(indexPath)"
return cell
}
}
I'll leave the "enable paging" part up to you :)

Resources