Here is my code:
import UIKit
import RxSwift
import RxCocoa
import RxOptional
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let items = Observable.just(
(0..<20).map{ "Test \($0)" }
)
items.asObservable().bindTo(self.collectionView.rx.items(cellIdentifier: CustomCollectionViewCell.reuseIdentifier, cellType: CustomCollectionViewCell.self)) { row, data, cell in
cell.data = data
}.addDisposableTo(disposeBag)
}
}
So how to show 3 columns in 1 row.
I can't find any tutorial about collection view with RxSwift.
Something like this:
class ViewController: UIViewController, UICollectionViewDelegateFlowLayout {
....
override func viewDidLoad() {
super.viewDidLoad()
...
items.asObservable().bindTo(self.collectionView.rx.items(cellIdentifier: CustomCollectionViewCell.reuseIdentifier, cellType: CustomCollectionViewCell.self)) { row, data, cell in
cell.data = data
}.addDisposableTo(disposeBag)
// add this line you can provide the cell size from delegate method
collectionView.rx.setDelegate(self).addDisposableTo(disposeBag)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.bounds.width
let cellWidth = (width - 30) / 3 // compute your cell width
return CGSize(width: cellWidth, height: cellWidth / 0.6)
}
}
Another alternative:
let flowLayout = UICollectionViewFlowLayout()
let size = (collectionView.frame.size.width - CGFloat(30)) / CGFloat(3)
flowLayout.itemSize = CGSize(width: size, height: size)
collectionView.setCollectionViewLayout(flowLayout, animated: true)
Related
Is there a way to measure the width and height of a cell defined in xib from within "sizeforitemat" function or any other function of viewcontroller, once data is dynamically generated? The default layout of the collection view makes it very shabby as depicted in output. Principally layout should have maximally two columns, and the rest of the empty spaces should be divided between cells within a row. So I want to loop over all the items, once it is filled with data to determine the maximum size of amongst cells to generate a layout according to its size. I'll highly appreciate the response.
viewcontroller.swift
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate,
UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionView: UICollectionView!
let myCell: String = "CollectionViewCell"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.collectionView.register(UINib(nibName:myCell, bundle: nil), forCellWithReuseIdentifier: myCell)
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
150
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: myCell, for: indexPath) as! CollectionViewCell
cell.lbl1.text = "\(indexPath.row)"
cell.lbl2.text = "\(String(describing: cell.lbl2.text))\(indexPath.row)"
print ("***", indexPath.item, cell.bounds.size, collectionView.frame.size)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// TODO: measure width and heigth of each cell to find number of columns
return collectionView.frame.size
}
}
collectionviewcell.swift
import UIKit
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var lbl1: UILabel!
#IBOutlet weak var lbl2: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
viewcontroller's code
xib file's code
collection view cell
output
To find the "widest" cell, you can:
create an instance of your cell
loop through your data, setting the label values
get the width
track the maximum width
Something like this:
let theData: [String] = [
"Short",
"Longer",
"A bit longer",
"ABC",
"This is the longest one",
"Another medium",
"Last one"
]
let nib: UINib = UINib(nibName: "CollectionViewCell", bundle: nil)
let c = nib.instantiate(withOwner: nil, options: nil).first as! CollectionViewCell
var sz: CGSize = .zero
var maxW: CGFloat = 0
for i in 0..<theData.count {
c.lbl1.text = "\(i)"
c.lbl2.text = theData[i]
sz = c.systemLayoutSizeFitting(CGSize(width: .greatestFiniteMagnitude, height: 60.0),
withHorizontalFittingPriority: .defaultLow,
verticalFittingPriority: .required)
maxW = max(maxW, sz.width)
print("Row: \(i)", sz)
}
print("Max Width:", maxW)
Could give this in the debug output:
Row: 0 (50.0, 60.0)
Row: 1 (61.5, 60.0)
Row: 2 (95.5, 60.0)
Row: 3 (42.0, 60.0)
Row: 4 (179.5, 60.0)
Row: 5 (134.5, 60.0)
Row: 6 (73.5, 60.0)
Max Width: 179.5
I created a UICollectionView Inside a ViewController but the UICollectionView is not loading the data I appended to the array. I tried to print from the UIcollectionView datasource and delegate but it also not printing
import UIKit
import Alamofire
import SwiftyJSON
import SDWebImage
class SelectCarViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var selectedcarManufacturalLebel: UILabel!
#IBOutlet weak var selectedcarPlateNumber: UILabel!
#IBOutlet weak var selectCarCarouselCollectionView: UICollectionView!
var selecrCarsURL = "https://my.api.mockaroo.com/cars.json?key=86f86980"
var selectCarArray : [SelectCarDataModel] = [SelectCarDataModel]()
let selectCarFromJSON = SelectCarDataModel()
override func viewDidLoad() {
super.viewDidLoad()
selectCarCarouselCollectionView.delegate = self
selectCarCarouselCollectionView.dataSource = self
//register custom sell nib file
selectCarCarouselCollectionView.register(UINib.init(nibName: "SelectCarViewCell", bundle: nil), forCellWithReuseIdentifier: "selectIdentifier")
//Add flowyout function
addFlowlayOut()
getselectCarData(url: selecrCarsURL)
selectCarCarouselCollectionView.reloadData()
}
//MARK:- Add carousel flowlayout for viewcontroller collectionview
func addFlowlayOut() {
let flowlayout = UPCarouselFlowLayout()
flowlayout.itemSize = CGSize(width: UIScreen.main.bounds.size.width - 60, height: selectCarCarouselCollectionView.frame.size.height)
flowlayout.scrollDirection = .horizontal
flowlayout.sideItemScale = 0.8
flowlayout.sideItemAlpha = 1.0
flowlayout.spacingMode = .fixed(spacing: 5.0)
selectCarCarouselCollectionView.collectionViewLayout = flowlayout
}
//MARK:- Select Car Newtworking
func getselectCarData(url: String) {
Alamofire.request(url, method: .get).responseJSON {
response in
if response.result.isSuccess {
print("Sucess Got the Selected Cars Data")
let selelectCarJSON : JSON = JSON(response.result.value!)
print(selelectCarJSON)
self.updateSelectedCar(json: selelectCarJSON)
}else {
print("error)")
}
}
}
//MARK:- Select Car Update JSON Parsing
func updateSelectedCar(json : JSON) {
//for i in 0...json.count-1 {
selectCarFromJSON.image = json["car"][0]["img"].stringValue
selectCarFromJSON.carType = json["car"][0] ["manufacturer"].stringValue
selectCarFromJSON.carModel = json["car"][0]["model"].stringValue
print("this is it \(selectCarFromJSON.image)")
//}
}
//MARK: -- UICollection Delegate and Datasourse Manipulation
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return selectCarArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = selectCarCarouselCollectionView.dequeueReusableCell(withReuseIdentifier: "selectIdentifier", for: indexPath) as! SelectCarViewCell
let selectCar = SelectCarDataModel()
selectCar.image = selectCarFromJSON.image
selectCar.carType = selectCarFromJSON.carType
selectCar.carModel = selectCarFromJSON.carModel
selectCarArray.append(selectCar)
print(selectCarArray)
cell.selectCarImage.sd_setImage(with: URL(string: selectCarArray[indexPath.row].image))
selectedcarManufacturalLebel.text = selectCarArray[indexPath.row].carType
selectedcarPlateNumber.text = selectCarArray[indexPath.row].carModel
print("this is cell\(selectCarArray[indexPath.row].image)")
selectCarCarouselCollectionView.reloadData()
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath.row)
}
}
I expected the SDWebImage to get the URL from the JSON and populate the UICollectionView
SDWebImage show URL image in imageView not a local image.
your selectCarArray store an image not url. so it's not working.
try this:-
cell.selectCarImage.sd_setImage(with: URL(string: "https://image.shutterstock.com/image-photo/beautiful-abstract-grunge-decorative-navy-260nw-539880832.jpg"))
I have UICollectionView in a cell of UITableView. This UICollectionView displays user's profile photos. There are only a few items there (let's assume 10 photos is a maximum).
So, I do not want to reload images from backend every time cell reusing. It's inconvenient and unfriendly UX. Therefore, I want to create 10 cells, load images into them and show.
But I don't know how to create (instead of dequeuing) new custom cells within "cellForItem" method of UICollectionView. Or how to forbid reusing.
I'm a kind of newbie in programming, so I would be glad if you explained me that in simple words and most detailed. Thank you.
Here is my code:
Setting CollectinView (uploading random local photos in testing purpose):
extension PhotosTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AddPhotoCollectionViewCell", for: indexPath)
return cell
} else {
var cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCollectionViewCell", for: indexPath) as! PhotoCollectionViewCell
cell.photo = UIImage(named: "Photo\(Int.random(in: 1...8))") ?? UIImage(named: "defaultPhoto")
cell.layer.cornerRadius = 10
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height = collectionView.frame.height
let width = (height / 16.0) * 10.0
return CGSize(width: width, height: height)
}
}
My custom PhotoCollectionViewCell:
class PhotoCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
#IBOutlet weak var widthConstraint: NSLayoutConstraint!
var photo: UIImage! {
didSet {
// simulate networking delay
perform(#selector(setImageView), with: nil, afterDelay: Double.random(in: 0.2...1))
// setImageView()
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#objc private func setImageView() {
let cellHeight = self.frame.height
let cellWidth = self.frame.width
let cellRatio = cellHeight / cellWidth
let photoRatio = photo.size.height / photo.size.width
var estimatedWidth = cellWidth
var estimatedHeight = cellHeight
if photoRatio > cellRatio {
estimatedWidth = cellWidth
estimatedHeight = cellWidth * photoRatio
} else {
estimatedHeight = cellHeight
estimatedWidth = cellHeight / photoRatio
}
widthConstraint.constant = estimatedWidth
heightConstraint.constant = estimatedHeight
imageView.image = photo
}
override func prepareForReuse() {
imageView.image = nil
}
}
Just create, fill and return new cell object in cellForItemAt instead of dequeueing it, but it's not a qood practice and you shouldn't do that.
You need to create new cell each time and assign the value to it. Although this is very dangerous thing to do and can cause your app to take so much memory over use your app will use more power and make the device slower.
Instead I suggest that you save your data into a Collection and to keep reusing it as you go.
I am trying to use a UICollectionView with a custom cell. My ViewController inherits from UICollectionViewDataSource as below:
import UIKit
import Parse
class DressingRoomViewController: UIViewController,
UICollectionViewDelegateFlowLayout,
UICollectionViewDataSource {
That inheritance is causing me to break a protocol of UICollectionViewDataSource with my UICollectionView function that creates and returns the custom cell. This is the function:
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath)
-> CustomCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
identifier,forIndexPath:indexPath) as! CustomCell
let dressingRoomIcons: [DressingRoomIcon] =
dataSource.dressingRoomIcons
let dressingRoomIcon = dressingRoomIcons[indexPath.row]
var imageView: MMImageView =
createIconImageView(dressingRoomIcon.name!)
cell.setImageV(imageView)
return cell
}
So before compilation the error is shown in the IDE. How do I get around this error? Here are the two errors I am experiencing:
Type 'DressingRoomViewController' does not conform to protocol
'UICollectionViewDataSource'
Cannot assign a value of type 'DressingRoomViewController' to a value
of type 'UICollectionViewDataSource?'
Here is the whole ViewController:
import UIKit
import Parse
class DressingRoomViewController: UIViewController,
UICollectionViewDelegateFlowLayout,
UICollectionViewDataSource {
#IBOutlet weak var MirrorImageView: UIImageView!
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
let identifier = "cellIdentifier"
let dataSource = DataSource()
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let cellSpacing: CGFloat = 5
let cellsPerRow: CGFloat = 6
let numberOfItems = 12
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
}
override func viewDidAppear(animated: Bool) {
let cellSize = (collectionView.collectionViewLayout
.collectionViewContentSize().width
/ cellsPerRow)
- (cellSpacing)
layout.itemSize = CGSize(width: cellSize, height: cellSize)
layout.minimumInteritemSpacing = cellSpacing
layout.minimumLineSpacing = cellSpacing
layout.scrollDirection = UICollectionViewScrollDirection.Horizontal
collectionView.collectionViewLayout = layout
self.heightConstraint.constant = cellSize
self.view.layoutIfNeeded()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue( segue: UIStoryboardSegue,
sender: AnyObject?) {
if (segue.identifier == "dressingRoom2MyOutfits") {
let myOutfitsViewController = segue.destinationViewController
as! MyOutfitsViewController
} else if (segue.identifier == "dressingRoom2StickerPicker") {
let myStickerPickerController = segue.destinationViewController
as! StickerPickerViewController
}
}
func imageTapped(sender: UITapGestureRecognizer) {
var imageView = sender.view as! MMImageView
println(imageView.fname)
performSegueWithIdentifier( "dressingRoom2StickerPicker",
sender: imageView)
}
}
// MARK:- UICollectionViewDataSource Delegate
extension DressingRoomViewController : UICollectionViewDataSource {
func numberOfSectionsInCollectionView(
collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath)
-> CustomCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
identifier,forIndexPath:indexPath) as! CustomCell
let dressingRoomIcons: [DressingRoomIcon] =
dataSource.dressingRoomIcons
let dressingRoomIcon = dressingRoomIcons[indexPath.row]
var imageView: MMImageView =
createIconImageView(dressingRoomIcon.name!)
cell.setImageV(imageView)
return cell
}
func createIconImageView(name: String) -> MMImageView{
var imageView :MMImageView =
MMImageView(frame:CGRectMake( 0,
0,
(collectionView.collectionViewLayout
.collectionViewContentSize().width / cellsPerRow)
- (cellSpacing),
(collectionView.collectionViewLayout
.collectionViewContentSize().width / cellsPerRow)
- (cellSpacing)))
imageView.contentMode = UIViewContentMode.ScaleAspectFit
imageView.image = UIImage(named: name)
imageView.setName(name)
imageView.backgroundColor = UIColor.clearColor()
imageView.userInteractionEnabled = true
var tapGestureRecognizer =
UITapGestureRecognizer(target: self, action: "imageTapped:")
tapGestureRecognizer.numberOfTapsRequired = 1
imageView.addGestureRecognizer(tapGestureRecognizer)
return imageView
}
}
EDIT: Here is my CustomCell:
import Foundation
import UIKit
class CustomCell: UICollectionViewCell {
var imageView = MMImageView()
func setImageV(IV: MMImageView) {
self.imageView = IV
}
}
Replace this Code
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath)
-> UICollectionViewCell {
Instead of below this :
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath)
-> CustomCell {
You can return custom cell but you can't change return type of any DataSource or Delegate method.
I have a UICollectionView that displays 12 images. The images are not fitting in the cells, they are being cropped, but I want them to fit in the cells without being cropped.
The code that should make the image scale to fit is:
cell.imageView.contentMode = UIViewContentMode.ScaleAspectFit
cell.imageView.image = UIImage(named: name)
Here is my code for the whole viewController class and screenshot:
import UIKit
class DressingRoomViewController:
UIViewController,
UICollectionViewDelegateFlowLayout,
UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
let identifier = "cellIdentifier"
let dataSource = DataSource()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
}
override func viewDidAppear(animated: Bool) {
// Notes on the equation to get the cell size:
// cells per row = 6
// cell spacing = 10
// collectionView.layout.inset = 20 (10 left, 10 right)
let cellSize = (collectionView.collectionViewLayout
.collectionViewContentSize().width - 20) / 6 - 10
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets( top: 20,
left: 10,
bottom: 10,
right: 10)
layout.itemSize = CGSize(width: cellSize, height: cellSize)
collectionView.collectionViewLayout = layout
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue( segue: UIStoryboardSegue,
sender: AnyObject?) {
if (segue.identifier == "dressingRoom2MyOutfits") {
let myOutfitsViewController = segue.destinationViewController
as! MyOutfitsViewController
}
}
}
// MARK:- UICollectionViewDataSource Delegate
extension DressingRoomViewController : UICollectionViewDataSource {
func numberOfSectionsInCollectionView(
collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
identifier,forIndexPath:indexPath) as! FruitCell
let fruits: [Fruit] = dataSource.fruits
let fruit = fruits[indexPath.row]
let name = fruit.name!
cell.imageView.contentMode = UIViewContentMode.ScaleAspectFit
cell.imageView.image = UIImage(named: name)
return cell
}
}
EDIT: Here is the FruitCell class, just in case you were wondering.
class FruitCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
}
A UIImageView that has been placed in a UICollectionViewCell from the interface builder, will not have any knowledge of a UICollectionViewFlowLayout that has been initialised programmatically. So the layout.sectionInset that I had set, was making the UICollectionViewCells smaller, and even when the UIImageView created in interface builder was constrained to the margins of the UICollectionViewCell, the UIImageView was not resizing to go smaller.
The solution was to initialise the UIImageView programmatically and set the size to be the size of the cell taking into account the cell spacing.
Here is the code:
import UIKit
class DressingRoomViewController:
UIViewController,
UICollectionViewDelegateFlowLayout,
UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
let identifier = "cellIdentifier"
let dataSource = DataSource()
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
let cellSpacing: CGFloat = 2
let cellsPerRow: CGFloat = 6
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
}
override func viewDidAppear(animated: Bool) {
let cellSize = (collectionView.collectionViewLayout
.collectionViewContentSize().width / cellsPerRow) - (cellSpacing)
layout.itemSize = CGSize(width: cellSize, height: cellSize)
layout.minimumInteritemSpacing = cellSpacing
layout.minimumLineSpacing = cellSpacing
collectionView.collectionViewLayout = layout
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue( segue: UIStoryboardSegue,
sender: AnyObject?) {
if (segue.identifier == "dressingRoom2MyOutfits") {
let myOutfitsViewController = segue.destinationViewController
as! MyOutfitsViewController
}
}
}
// MARK:- UICollectionViewDataSource Delegate
extension DressingRoomViewController : UICollectionViewDataSource {
func numberOfSectionsInCollectionView(
collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return 12
}
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
identifier,forIndexPath:indexPath) as! FruitCell
let fruits: [Fruit] = dataSource.fruits
let fruit = fruits[indexPath.row]
let name = fruit.name!
var imageView :UIImageView
imageView = UIImageView(frame:CGRectMake( 0,
0,
(collectionView.collectionViewLayout
.collectionViewContentSize().width / cellsPerRow)
- (cellSpacing),
(collectionView.collectionViewLayout
.collectionViewContentSize().width / cellsPerRow)
- (cellSpacing)))
imageView.contentMode = UIViewContentMode.ScaleAspectFit
imageView.image = UIImage(named: name)
imageView.backgroundColor = UIColor.redColor()
cell.addSubview(imageView)
return cell
}
}
EDIT: I was able to crop off the empty space at the bottom of the UICollectionView by creating an outlet for the height constraint ( control + drag height constraint in interface builder onto viewController swift file) calling it heightConstraint, and then at the bottom of viewDidAppear added these two lines of code:
self.heightConstraint.constant = collectionView.contentSize.height
self.view.layoutIfNeeded()
So to show you the result with a white background on the UICollectionView and a red background on the UIImageViews, here is the result (also works on all other device sizes):