Fairly new to swift and just getting into the design/app making aspect of it. So I want to make a 9X9 grid of text fields for a sudoku solving app, where each text field will take in a number and assign the value to a struct I have created. I am having a hard time finding the correct way to do this. I feel like I could just make 81 text boxes and assign each of them seperately, but I feel like as a programmer this is definitely the wrong way to go about this. Could anyone point me in the right direction?
Thanks!
UICollectionView is the way to go.
If you want a 9*9 grid with a header cell on top then something like this would work:
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 82
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if indexPath.row == 0
{
return CGSize(width: screenWidth, height: screenWidth/3)
}
return CGSize(width: screenWidth/9, height: screenWidth/9);
}
That gives you:
Complete viewController with an embedded UICollectionView:
import UIKit
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var collectionView : UICollectionView? // Optional
var screenSize : CGRect!
var screenWidth : CGFloat!
var screenHeight : CGFloat!
override func viewDidLoad() {
super.viewDidLoad()
screenSize = self.view.frame
screenWidth = screenSize.width
screenHeight = screenSize.height
// Do any additional setup after loading the view, typically from a nib.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 0, bottom: 10, right: 0)
layout.itemSize = CGSize(width: screenWidth/3, height: screenWidth/3)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
print(collectionView?.frame.width)
collectionView!.dataSource = self
collectionView!.delegate = self
collectionView!.registerClass(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
collectionView!.backgroundColor = UIColor.greenColor()
self.view.addSubview(collectionView!)
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 82
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if indexPath.row == 0
{
return CGSize(width: screenWidth, height: screenWidth/3)
}
return CGSize(width: screenWidth/9, height: screenWidth/9);
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as! CollectionViewCell
if indexPath.row == 0
{
cell.backgroundColor = UIColor.lightGrayColor()
}else
{
cell.backgroundColor = UIColor.whiteColor()
}
cell.layer.borderColor = UIColor.blackColor().CGColor
cell.layer.borderWidth = 0.5
//cell.textLabel?.text = "\(indexPath.section):\(indexPath.row)"
return cell
}
}
Related
How can I build a UICollectionView similar to the Instagram one?
I already created the three columns one:
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let width = (view.frame.width/3)-2
myCollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
layout.sectionInset = UIEdgeInsets(top: 49, left: 1, bottom: 1.5, right: 1)
layout.itemSize = CGSize(width: width, height: width)
layout.minimumInteritemSpacing = 1
layout.minimumLineSpacing = 1
myCollectionView?.dataSource = self
myCollectionView?.delegate = self
myCollectionView?.register(PhotoGalleryCell.self, forCellWithReuseIdentifier: cellid)
myCollectionView?.backgroundColor = .white
self.view.addSubview(myCollectionView!)
myCollectionView!.addSubview(segmentedControl)
segmentedControl.topAnchor.constraint(equalTo: myCollectionView!.topAnchor, constant: 12).isActive = true
segmentedControl.centerXAnchor.constraint(equalTo: myCollectionView!.centerXAnchor).isActive = true
segmentedControl.heightAnchor.constraint(equalToConstant: 25).isActive = true
segmentedControl.widthAnchor.constraint(equalTo: myCollectionView!.widthAnchor, constant: -24).isActive = true
But I want the Collection view's items to become a 1 column width when the selectedControl's value becomes 1, and then if the value goes back to 0 the collectionView goes back to a 3 columns setup.
Is there a way to do this?
You need to change the layout of the collectionView based on the expected UI - grid or list, when switching between the segments of UISegmentedControl.
First of all, create an enum LayoutType like,
enum LayoutType {
case list, grid
}
Now, in your ViewController, create a property layoutType of type LayoutType initially set to .list, i,e.
var layoutType = LayoutType.list
Next, implement the #IBAction for segmentedControl's valueChanged event. So, till now the ViewController looks like.
class VC: UIViewController, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionView: UICollectionView!
var layoutType = LayoutType.list
#IBAction func onChange(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
layoutType = .list
} else {
layoutType = .grid
}
collectionView.reloadData()
}
}
Now implement the UICollectionViewDataSource methods as required. You must've done that already.
Now to change the layout based on the layoutType, you need to conform to UICollectionViewDelegateFlowLayout protocol and implement the below methods,
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
switch layoutType {
case .list:
return CGSize(width: collectionView.bounds.width, height: 50)
case .grid:
return CGSize(width: (collectionView.bounds.width - 20) / 3, height: 50)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
This is how you can proceed with the things. Let me know in case you still face any issue.
I have a collectionview with two sections, each sections have one item. The cell in section contains collectionview and I need to fit height cell to content collectionview.
My first section has a fixed height to 120 because it's horizontal collectionview and height not changed. But for second section number of elements can be changed and I need to display all items (collectionview have scrollEnabled to false and scrolldirecton to vertical).
My problem is I need to display a collectionview with two scrolldirection depends sections and display all items for vertical collectionview.
When I do collectionview.collectionViewLayout.collectionViewContentSize.height I have the good height but I don't know how passed this height to parent viewcontroller. I search a dynamic solution to fixed this.
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
// MARK: - UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return presenter.numberOfSections // == 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return presenter.numberOfItemsInSection // == 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueCell(InlineDisplayCollectionViewCell.self, for: indexPath)
configureCell(cell, at: indexPath)
return cell
}
// MARK: - UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let type = presenter.typeAtIndexPath(indexPath)
switch type {
case .city:
return CGSize(width: collectionView.frame.width, height: 120)
case .suggestion:
return CGSize(width: collectionView.frame.width, height: 820)
default:
return CGSize.zero
}
}
}
class InlineDisplayCollectionViewCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionView: UICollectionView!
var itemType: ItemModelCollectionType! = .city {
didSet {
updateCell()
}
}
var cities: [City]? {
didSet {
collectionView.reloadData()
}
}
var suggestions: [Suggestion]? {
didSet {
collectionView.reloadData()
}
}
override func awakeFromNib() {
super.awakeFromNib()
configureView()
}
// MARK: - UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return itemType == .city ? cities?.count ?? 0 : suggestions?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: UICollectionViewCell
if itemType == .city {
let cityCell = collectionView.dequeueCell(CityCollectionViewCell.self, for: indexPath)
configure(cell: cityCell, at: indexPath)
cell = cityCell
} else {
let suggestionCell = collectionView.dequeueCell(SquareCollectionViewCell.self, for: indexPath)
configure(cell: suggestionCell, at: indexPath)
cell = suggestionCell
}
return cell
}
// MARK: - UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return itemType == .city ? 10 : 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return itemType == .city ? 0 : 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size: CGSize
if itemType == .city {
size = CGSize(width: 100, height: 120)
} else {
let width = (collectionView.frame.width - (collectionView.contentInset.left + collectionView.contentInset.right) - 10) * 0.5
let height = width
size = CGSize(width: width, height: height)
}
return size
}
// MARK: - Private functions
private func configureView() {
collectionView.delegate = self
collectionView.dataSource = self
collectionView.contentInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
collectionView.registerNib(CityCollectionViewCell.self, bundle: nil)
collectionView.registerNib(SquareCollectionViewCell.self, bundle: nil)
}
private func updateCell() {
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = itemType == .city ? .horizontal : .vertical
}
collectionView.isScrollEnabled = itemType == .city
}
private func configure(cell: CityCollectionViewCell, at indexPath: IndexPath) {
guard let city = cities?[indexPath.row] else { return }
cell.configure(with: city)
}
private func configure(cell: SquareCollectionViewCell, at indexPath: IndexPath) {
guard let suggestion = suggestions?[indexPath.row] else { return }
cell.configure(with: suggestion)
}
}
You can't do that. CollectionView cells are dequeued and reused dynamically. The best way to go about it is to use different types of cells within the same CollectionView.
Please check the questions I have posted on this topic: Get the sum of all collectionViewCells' heights inside a CollectionView and Multiple Cells for Comments and Reply in a CollectionView
Unfortunately you can't do something like that. But you can have different types of cells inside your CV. Also you can modify that in the function called "cellForItemAt".
I am placing a horizontal scroll UICollectionViewController inside of another UICollectionViewController cell. I am doing this because I want to create an image slider like effect.
Code for horizontal scroll UICollectionViewController:
class BusinessPhotosViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout{
var photos = [String]()
let cellId = "photos"
override func viewDidLoad() {
super.viewDidLoad()
if let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
}
collectionView?.backgroundColor = .red
collectionView?.showsVerticalScrollIndicator = false
collectionView?.showsHorizontalScrollIndicator = false
collectionView?.alwaysBounceVertical = false
collectionView?.isPagingEnabled = true
collectionView?.isScrollEnabled = true
collectionView?.register(BusinessPhotoCells.self, forCellWithReuseIdentifier: cellId)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! BusinessPhotoCells
if photos.count != 0 {
let imageUrl = photos[indexPath.item]
let url = URL(string: imageUrl)
cell.logoImage.kf.setImage(with: url)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.height-10, height: view.frame.height-10)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
}
In another class I add this horizontal scroll UICollectionViewController as such:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//ADDING SCROLL CELL HERE
if indexPath.section == 1 {
let scrollCell = collectionView.dequeueReusableCell(withReuseIdentifier: pictureCellId, for: indexPath)
scrollCell.backgroundColor = .blue
let bizVC = BusinessPhotosViewController(collectionViewLayout: UICollectionViewFlowLayout())
scrollCell.addSubview(bizVC.view)
bizVC.view.anchor(top: scrollCell.topAnchor, left: scrollCell.leftAnchor, bottom: scrollCell.bottomAnchor, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: scrollCell.frame.width, height: scrollCell.frame.height)
return scrollCell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: listCellId, for: indexPath) as! CouponCell
return cell
}
Edit: Adding numberOfItemsInSection per comments
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 {
return 0
}
if section == 1 {
return 1
}
return coupons.count
}
I return 1 for this section because I only want 1 horizontal scroll cell...Below that horizontal scroll cell (in section 2) I then have a list of vertical scroll cells that work as expected.
This results in something like this where only 3 squares are loaded up not the 10 as expected:
EDIT: ADDING IMAGE OF WHAT MY VIEW LOOKS LIKE NOW
I don't understand what's happening with my UICollectionView.
I'm apologise in advance because I think the answer is very simple
I just wan't to create a UICollectionView programmatically with 4 cells but 2 of them does not appear I don't know why
Here my code:
class NewsListViewController: UIViewController {
public var newsImagesCollectionView: UICollectionView!
override func viewWillLayoutSubviews() {
if self.newsImagesCollectionView == nil {
self.createNewsImagesCollectionView()
}
}
fileprivate func createNewsImagesCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
self.newsImagesCollectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
self.newsImagesCollectionView.dataSource = self
self.newsImagesCollectionView.delegate = self
self.newsImagesCollectionView.register(NewsImagesCollectionViewCell.self, forCellWithReuseIdentifier: NewsImagesCollectionViewCell.reuseIdentifier)
self.newsImagesCollectionView.backgroundColor = .yellow
self.newsImagesCollectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.newsImagesCollectionView)
self.newsImagesCollectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.newsImagesCollectionView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.newsImagesCollectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.newsImagesCollectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
}
}
extension NewsListViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NewsImagesCollectionViewCell.reuseIdentifier, for: indexPath) as! NewsImagesCollectionViewCell
switch indexPath.row {
case 0:
cell.imageView.backgroundColor = .purple
case 1:
cell.imageView.backgroundColor = .green
case 2:
cell.imageView.backgroundColor = .orange
case 3:
cell.imageView.backgroundColor = .red
default:
cell.imageView.backgroundColor = .blue
}
cell.imageView.contentMode = .scaleAspectFit
cell.imageView.image = #imageLiteral(resourceName: "euro")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
print(self.view.frame.width)
return CGSize(width: self.view.frame.width / 4, height: self.view.frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
And I get this:
enter image description here
Thanks for your help
Does your collection view has the same size than your main view? because you calculating the size with the main view, is better if you use the collection view size.
return CGSize(width: collectionView.frame.width / 4, height: collectionView frame.height)
I want to custom a lot of layout size for each row of my collectionView by using UICollectionViewDelegateFlowLayout like below:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = view.frame.width
if indexPath.row == 0 {
return CGSize(width: width, height: 300)
}else if indexPath.row == 1 {
return dynamicSize // i want this size wrap its content
}
return CGSize(width: width, height:100)
}
I want dynamicSize that can wrap its content for some row.
Anyone know how to do this?
Here is image of cell that i want dynamic size to wrap it.
I suggest you to use tableview instead of collectionview. It is very easy for you.
And for cellForHeight returns UITableViewAutomaticDimension. It will calculate your cell height itself and adjust it accordingly.
Note: Make sure your constraints are proper.
Edit: By using collectionview with two columns.
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var text = "Do any additional setup after loading the view, typically from a nib."
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets{
return UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
let cellWidth = self.view.frame.size.width/2 - 10
let textHeight = text.height(withConstrainedWidth: cellWidth, font: UIFont.systemFont(ofSize: 16))
let constantHeight : CGFloat = 40 + 25 + 10 // top name view + bottom like button + margins height
let totHeight = constantHeight + textHeight
return CGSize(width: cellWidth, height: totHeight)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCollectionViewCell", for: indexPath) as! MyCollectionViewCell
cell.label.text = text
cell.layer.borderWidth = 1.0
cell.layer.borderColor = UIColor.black.cgColor
return cell
}
}
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: font], context: nil)
return ceil(boundingBox.height)
}
func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: font], context: nil)
return ceil(boundingBox.width)
}
}
Output:
Use this delegate method. this will work for me.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: Your cellWidth, height: YourcellHeight);
}