Collection View as Custom Keyboard not working - ios

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.

Related

UIImageView not resizing as circle and UILabel not resizing within StackView and Custom Collection Cell

I am trying to resize my UIImageView as a circle, however; every time I try to resize the UIImageView, which is inside a StackView along with the UILabel, I keep on ending up with a more rectangular shape. Can someone show me where I am going wrong I have been stuck on this for days? Below is my code, and what it's trying to do is add the image with the label at the bottom, and the image is supposed to be round, and this is supposed to be for my collection view controller.
custom collection view cell
import UIKit
class CarerCollectionViewCell: UICollectionViewCell {
static let identifier = "CarerCollectionViewCell"
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.frame = CGRect(x: 0, y: 0, width: 20, height: 20);
//imageView.center = imageView.superview!.center;
imageView.contentMode = .scaleAspectFill
imageView.layer.borderWidth = 4
imageView.layer.masksToBounds = false
imageView.layer.borderColor = UIColor.orange.cgColor
imageView.layer.cornerRadius = imageView.frame.height / 2
return imageView
}()
private let carerNamelabel: UILabel = {
let carerNamelabel = UILabel()
carerNamelabel.layer.masksToBounds = false
carerNamelabel.font = .systemFont(ofSize: 12)
carerNamelabel.textAlignment = .center
carerNamelabel.layer.frame = CGRect(x: 0, y: 0, width: 50, height: 50);
return carerNamelabel
}()
private let stackView: UIStackView = {
let stackView = UIStackView()
stackView.layer.masksToBounds = false
stackView.axis = .vertical
stackView.alignment = .center
stackView.backgroundColor = .systemOrange
stackView.distribution = .fillProportionally
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
override init(frame: CGRect) {
super.init(frame: frame)
configureContentView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func configureContentView() {
imageView.clipsToBounds = true
stackView.clipsToBounds = true
carerNamelabel.clipsToBounds = true
contentView.addSubview(stackView)
configureStackView()
}
private func configureStackView() {
allContraints()
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(carerNamelabel)
}
private func allContraints() {
setStackViewConstraint()
}
private func setStackViewConstraint() {
stackView.widthAnchor.constraint(equalTo: contentView.widthAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: contentView.heightAnchor).isActive = true
stackView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
public func configureImage(with imageName: String, andImageName labelName: String) {
imageView.image = UIImage(named: imageName)
carerNamelabel.text = labelName
}
override func layoutSubviews() {
super.layoutSubviews()
stackView.frame = contentView.bounds
}
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
carerNamelabel.text = nil
}
}
Below here is my code for the CollectionViewControler
custom collection view
import UIKit
class CarerViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: 120, height: 120)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(CarerCollectionViewCell.self, forCellWithReuseIdentifier: CarerCollectionViewCell.identifier)
collectionView.showsVerticalScrollIndicator = false
collectionView.backgroundColor = .clear
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for `collectionView`
NSLayoutConstraint.activate([
collectionView.widthAnchor.constraint(equalTo: view.widthAnchor),
collectionView.heightAnchor.constraint(lessThanOrEqualTo: view.heightAnchor, constant: 600),
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CarerCollectionViewCell.identifier, for: indexPath) as! CarerCollectionViewCell
cell.configureImage(with: "m7opt04g_ms-dhoni-afp_625x300_06_July_20", andImageName: "IMAGE NO. 1")
return cell
}
}
Can somebody show or point to me what I am doing wrong, thank you
This is what I am trying to achieve
UIStackView could be tricky sometimes, you can embed your UIImageView in a UIView, and move your layout code to viewWillLayoutSubviews(), this also implys for the UILabel embed that also inside a UIView, so the containers UIViews will have a static frame for the UIStackViewto be layout correctly and whats inside them will only affect itself.

Change layout of UICollectionViewCell

I am using a UICollectionViewController to display a collection of UICollectionViewCell that a user can swipe between, on ios 13, swift 5, and UIKit. I ultimatelly want to re-draw the cell if the user has changed the orientation. In my controller, I have noticed that this piece of code gets executed on orientation change:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! MyCell
return cell
}
In MyCell, I am using this code to monitor then a layout is re-initialized:
override init(frame: CGRect) {
super.init(frame: frame)
setLayout()
print("### orientation:: \(UIDevice.current.orientation.isLandscape) ")
}
Result: This method is called 2 times. When the app starts, and when I change from portrait to landscape. Any subsequent changes, do not call it. I suspect that this is fine, as maybe both versions are cached ( a landscape and a portrait ). If I add code to the setLayout() method based on the UIDevice.current.orientation.isLandscape flag, nothing changes while it executes. Note: The actual result of UIDevice.current.orientation.isLandscape is correct.
Here is an example of what is in my setLayout() method:
let topImageViewContainer = UIView();
topImageViewContainer.translatesAutoresizingMaskIntoConstraints = false
topImageViewContainer.backgroundColor = .yellow
addSubview(topImageViewContainer)
NSLayoutConstraint.activate([
topImageViewContainer.topAnchor.constraint(equalTo: topAnchor),
topImageViewContainer.leftAnchor.constraint(equalTo: leftAnchor),
topImageViewContainer.rightAnchor.constraint(equalTo: rightAnchor),
topImageViewContainer.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5)
])
if (UIDevice.current.orientation.isLandscape) {
let imageView = UIImageView(image: UIImage(named: "photo_a"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
topImageViewContainer.addSubview(imageView)
} else {
let imageView = UIImageView(image: UIImage(named: "photo_b"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
topImageViewContainer.addSubview(imageView)
}
In the example above, the code with "photo_a" is executing, but the ui is still showing photo_b. I suspect I am doing something wrong, and I need to somehow migrate towards an "Adaptive layout" as mentioned here: Different layouts in portrait and landscape mode . However, I am not sure how I could hook in some "viewWillTransition*" functions into a UICollectionViewCell that has a UIView.
How can I adjust my MyCell class, that implements UICollectionViewCell, to allow the cell to be updated when the user changes from landscape to protrait in a Swift intended way?
Hi try to check if is landscape or portrait in cellForItem, my example:
set colloectionView, flowLayaout and cell id:
class ViewController: UIViewController {
private var collectionView: UICollectionView?
let layout = UICollectionViewFlowLayout()
let cellId = "cellId"
now in viewDidLoad set collection view and layout parameters and the collection constraints:
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView?.delegate = self
collectionView?.dataSource = self
collectionView?.backgroundColor = .clear
collectionView?.register(BottomCellTableViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.contentInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
collectionView?.showsHorizontalScrollIndicator = false
collectionView?.translatesAutoresizingMaskIntoConstraints = false
guard let collection = collectionView else { return }
view.addSubview(collection)
layout.itemSize = CGSize(width: view.bounds.size.width / 2, height: 200)
collection.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = true
collection.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
collection.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
collection.heightAnchor.constraint(equalToConstant: 200).isActive = true
after that conform to delegate and datasource in a separate extension (more clean code) and set in it numberOfItems and cellForRow:
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: cellId, for: indexPath) as! BottomCellTableViewCell
// check here device orientation
if UIDevice.current.orientation.isLandscape {
let image = UIImage(named: "meB")
cell.imageV.image = image
} else {
let image = UIImage(named: "me")
cell.imageV.image = image
}
// reload the view and the collection
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
return cell
}
}
In collectionViewCell adding a subview directly to the cell, and pinning the image view to the cell, is wrong. You add it to a contentView and pin to it relative constraints:
class BottomCellTableViewCell: UICollectionViewCell {
let imageV: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "me")
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .red
contentView.addSubview(imageV)
imageV.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
imageV.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
imageV.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
imageV.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is the result:

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

Why UICollectionView is not responding at all?

I'm setting up a UICollectionView inside a ViewController. However, it won't respond to any user interaction, didSelectItemAt function is not getting called and I am unable to scroll it.
I have set the DataSource and Delegate properly inside the viewDidLoad(). Here is my code:
class WelcomeController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let padding: CGFloat = 30
var sources = [Source]()
let sourceCellId = "sourceCellId"
func fetchSources() {
ApiSourceService.sharedInstance.fetchSources() { (root: Sources) in
self.sources = root.source
self.sourceCollectionView.reloadData()
}
}
let backgroundImage: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "background")
iv.contentMode = .scaleAspectFill
iv.translatesAutoresizingMaskIntoConstraints = false
iv.clipsToBounds = false
return iv
}()
let overlayView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
return view
}()
let logo: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "mylogo")
iv.translatesAutoresizingMaskIntoConstraints = false
iv.clipsToBounds = false
return iv
}()
let defaultButton: UIButton = {
let ub = UIButton()
ub.translatesAutoresizingMaskIntoConstraints = false
ub.setTitle("Inloggen met E-mail", for: .normal)
ub.setImage(UIImage(named: "envelope"), for: .normal)
ub.imageEdgeInsets = UIEdgeInsetsMake(15, 0, 15, 0)
ub.imageView?.contentMode = .scaleAspectFit
ub.contentHorizontalAlignment = .left
ub.titleLabel?.font = UIFont.init(name: "Raleway-Regular", size: 16)
ub.backgroundColor = .cuOrange
return ub
}()
let continueWithoutButton: UIButton = {
let ub = UIButton()
ub.translatesAutoresizingMaskIntoConstraints = false
let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: "Doorgaan zonder in te loggen")
let textRange = NSMakeRange(0, attributedString.length)
attributedString.setColor(color: .cuOrange, forText: "Doorgaan")
ub.setAttributedTitle(attributedString, for: .normal)
ub.contentHorizontalAlignment = .center
ub.titleLabel?.font = UIFont.init(name: "Raleway-Regular", size: 16)
ub.titleLabel?.textColor = .white
return ub
}()
let sourceCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .white
return cv
}()
let termsButton: UIButton = {
let ub = UIButton()
ub.translatesAutoresizingMaskIntoConstraints = false
let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: "Met het maken van een account \nof bij inloggen, ga ik akkoord \nmet servicevoorwaarden.")
let textRange = NSMakeRange(0, attributedString.length)
attributedString.setColor(color: .cuOrange, forText: "servicevoorwaarden")
ub.setAttributedTitle(attributedString, for: .normal)
ub.contentHorizontalAlignment = .center
ub.contentVerticalAlignment = .bottom
ub.titleLabel?.lineBreakMode = .byWordWrapping
ub.titleLabel?.font = UIFont.init(name: "Raleway-Regular", size: 14)
ub.titleLabel?.textColor = .white
ub.titleLabel?.textAlignment = .center
return ub
}()
override func viewDidLoad() {
super.viewDidLoad()
fetchSources()
sourceCollectionView.dataSource = self
sourceCollectionView.delegate = self
sourceCollectionView.register(SourceCell.self, forCellWithReuseIdentifier: sourceCellId)
view.addSubview(backgroundImage)
view.addSubview(overlayView)
backgroundImage.addSubview(termsButton)
backgroundImage.addSubview(logo)
backgroundImage.addSubview(sourceCollectionView)
backgroundImage.widthAnchor.constraint(lessThanOrEqualToConstant: 375).isActive = true
backgroundImage.heightAnchor.constraint(lessThanOrEqualToConstant: 667).isActive = true
backgroundImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
backgroundImage.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
logo.centerXAnchor.constraint(equalTo: backgroundImage.centerXAnchor).isActive = true
logo.topAnchor.constraint(equalTo: backgroundImage.topAnchor, constant: padding).isActive = true
logo.widthAnchor.constraint(equalToConstant: 107).isActive = true
logo.heightAnchor.constraint(equalToConstant: 100).isActive = true
sourceCollectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
sourceCollectionView.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -(padding*1.5)).isActive = true
sourceCollectionView.topAnchor.constraint(equalTo: logo.bottomAnchor, constant: padding).isActive = true
sourceCollectionView.bottomAnchor.constraint(equalTo: termsButton.topAnchor, constant: -(padding*2)).isActive = true
termsButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -padding).isActive = true
termsButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
termsButton.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -(padding*2)).isActive = true
termsButton.heightAnchor.constraint(equalToConstant: 40).isActive = true
sourceCollectionView.reloadData()
}
#objc func closeWelcomeController() {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.switchViewControllers()
self.dismiss(animated: true, completion: {
})
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = self.sourceCollectionView.frame.width
let height = CGFloat(50)
return CGSize(width: width, height: height)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return sources.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print(sources[indexPath.item].feed_name)
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: sourceCellId, for: indexPath) as! SourceCell
cell.preservesSuperviewLayoutMargins = true
cell.source = sources[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("selecting")
}}
Anyone knows what I am doing wrong here?
I hope there isn't just a "dumb" error inside the code why it's not working, but it's been eating my brain for the past few hours.
It seems that it is inappropriate to add sourceCollectionView as a subview in backgroundImage which is UIImageView! Logically, image view doesn't represent a container view (such as UIView UIStackView, UIScrollView) i.e even if the code lets you do that, it still non-sensible; Furthermore, even if adding a subview to an image view is ok (which is not), image views by default are user interaction disabled (userInteractionEnabled is false by default for image views), that's why you are unable to even scroll the collection view, it is a part (subview) of a disabled user interaction component.
In your viewDidLoad(), you are implementing:
backgroundImage.addSubview(termsButton)
backgroundImage.addSubview(logo)
backgroundImage.addSubview(sourceCollectionView)
Don't do this, instead, add them to main view of the view controller:
view.addSubview(termsButton)
view.addSubview(logo)
view.addSubview(sourceCollectionView)
For the purpose of checking if its the reason of the issue, at least do it for the collection view (view.addSubview(sourceCollectionView)).
And for the purpose of organizing the hierarchy of the view (which on is on top of the other), you could use both: bringSubview(toFront:) or sendSubview(toBack:). For instance, after adding the collection view into the view, you might need to:
view.bringSubview(toFront: sourceCollectionView)
to make sure that the collection view is on top of all components.
I see you add sourceCollectionView to subview of backgroundImage. Your backgroundImage is UIImageView so UIImageView doesn't have user interaction except you need to enable it. The problem of your code is
backgroundImage.addSubview(sourceCollectionView)
For my suggestion you should create UIView or other views that can interact with user interaction. It will work fine.

How to add a UICollectionView to a container programmatically?

I'm new to IOS development.
The followings are what I want to implement
- Have a two container in the main view
- In the first container, add a UICollectionView
- In the second container, add a UITableView
This is the code that I've done so far.
Main View:
class MainViewController:UIViewController {
let itemView:UIView = {
let itemContainerView = UIView()
itemContainerView.backgroundColor = UIColor.lightGray
let collectionView = CollectionViewController()
itemContainerView.addSubview(collectionView.view)
itemContainerView.translatesAutoresizingMaskIntoConstraints = false
return itemContainerView
}()
let tableView:UIView = {
let tableContainerView = UIView()
tableContainerView.backgroundColor = UIColor.gray
tableContainerView.translatesAutoresizingMaskIntoConstraints = false
return tableContainerView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
view.addSubview(itemView)
view.addSubview(tableView)
setupItemView()
setupTableView()
}
func setupItemView(){
itemView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
itemView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
itemView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 2/3).isActive = true
itemView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}
func setupTableView() {
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leftAnchor.constraint(equalTo: itemView.rightAnchor).isActive = true
tableView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1/3).isActive = true
tableView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}
}
CollectionViewController:
class CollectionViewController:UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
let cellId = "CellId"
var colView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: 111, height: 111)
colView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
colView.delegate = self
colView.dataSource = self
colView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
colView.backgroundColor = UIColor.white
colView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(colView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
cell.backgroundColor = UIColor.brown
return cell
}
}
I could see my main screen has two container but couldn't see 10 collection cells.
Could anyone advise me to the right direction? Thanks in advance.
UPDATED
When I set MainViewController as the initial view, it's properly working.
However, in my project, MainViewController is not the initial view.
The flow is;
1. User login
2. Dashboard with a button will appear
3. Clicking button will navigate the user to MainViewController.
Here's my Dashboard class
class DashboardController: UIViewController{
let orderBtn:UIButton = {
let button = UIButton(type: .system)
button.backgroundColor = UIColor.init(red: 173/255, green: 184/255, blue: 255/255, alpha: 1)
button.layer.cornerRadius = 5
button.setTitle("Select Item" , for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(handleOrderNavigation), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
view.addSubview(orderBtn)
setupOrderBtn()
}
func handleOrderNavigation() {
let mainViewController= MainViewController()
let mainViewNavController= UINavigationController(rootViewController: mainViewController)
self.present(mainViewNavController, animated: false, completion: nil)
}
}
Your code is proper working. here is the output I got from your collectionView code work.
Suggestion
Check view hierarchy of your UI and see is there is any collectionView present in your UI.
Ensure your methods are properly calling. Check it by adding break point.
Edit
You declaration of MainViewController is wrong in button action.
Instead of present MainViewController use push to MainViewController
Be ensure your button action method is properly calling on button click.
Try to call reloadData at some point after the view is loaded.

Resources