How can I use scrollView inside collectionViewCell with dynamic size - swift

I want to create this page. And I created a collectionView inside viewController. But how can I make the height of this dynamic and i think i need to create a scroll view? How do I change the collection view size than json data loaded? And how should the structure on this page?
I created programmatically collection view but I don't have any data now. But when I create collection view I have to give height value. I must when I loaded json data than I will set frame details. Please help this topics.. At least give mind please.. Thank you all
Edit:
I added scroll view inside cell but switch between cells not working now. In red area(cell) working switching but not working on containerView..
Scroll View problem
class NewsDetailController: UIViewController {
private lazy var collectionView: UICollectionView = {
let frame = CGRect(x: 0, y: 100, width: view.frame.width, height: 800)
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: frame, collectionViewLayout: layout)
cv.isPagingEnabled = true
cv.delegate = self
cv.dataSource = self
cv.showsHorizontalScrollIndicator = false
cv.register(NewsDetailCell.self, forCellWithReuseIdentifier: reuseIdentifier)
return cv
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}
// MARK: - Helpers
func configureUI() {
view.addSubview(collectionView)
view.backgroundColor = .white
view.addSubview(stackBottomButtons)
stackBottomButtons.anchor(left: view.leftAnchor,bottom: view.bottomAnchor,right: view.rightAnchor,paddingLeft: 16,paddingBottom: 32,paddingRight: 16, height: 60)
backButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
shareButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! NewsDetailCell
return cell
}
}
extension NewsDetailController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 800)
}
}
class NewsDetailCell: UICollectionViewCell {
// MARK: - Properties
let contentLabel: UILabel = {
let label = UILabel(frame: CGRect.zero)
label.numberOfLines = 0
return label
}()
// MARK: - Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
configure()
configureUI()
}
func configure() {
contentLabel.text = articleViewModel?.article.content
}
func configureUI() {
addSubview(contentLabel)
contentLabel.anchor(top: stack.bottomAnchor,left: leftAnchor,right: rightAnchor)
}
}
I want to make this page.
Full Code

I think you are looking for something like this
To make the frame size dynamic you you need to set the cell size by the attributes combined height inside of the cells this can be done in the customCell file you create using this link

Related

Updating height constraint of UICollectionView not working

I have this collection view inside my collection view cell, of which I will populate with more cells. I'm currently trying to update the height of the UICollectionView upon any editing events to the inner cells text field.
This currently works when I load the page, click into the field and then scroll. However, if I load the page, scroll first and then click into the field, it doesn't work but I'm unsure why. I can't see anything that is running differently.
Any help would be appreciated. Thanks.
import UIKit
class MyCollectionViewCell: UICollectionViewCell {
var heightAnchor: NSLayoutConstraint?
var collectionView: UICollectionView!
static let reuseIdentifier: String = "MyCollectionViewCell"
override func awakeFromNib() {
super.awakeFromNib()
var layout: UICollectionViewLayout = UICollectionViewLayout()
collectionView = UICollectionView(frame: contentView, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.isScrollEnabled = false
self.contentView.addSubview(collectionView)
heightAnchor = collectionView.heightAnchor.constraint(equalToConstant: 600)
heightAnchor?.isActive = true
collectionView.anchorViewPosition(parent: self.contentView, leading: .zero, top: .zero, bottom: .zero)
collectionView.register(UINib(nibName: "MyInnerCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: MyInnerCollectionViewCell.reuseIdentifier)
}
override func layoutSubviews() {
super.layoutSubviews()
collectionview.widthAnchor.constraint(equalToConstant: 288).isActive = true
}
#objc func updateCollectionViewSize() {
heightAnchor?.constant = 200
}
}
extension MyCollectionViewCell: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyInnerCollectionViewCell", for: indexPath) as? MyInnerCollectionViewCell
cell?.field.addTarget(self, action: #selector(updateCollectionViewSize()), for: .allEditingEvents)
return cell!
}
}

Collectionview in NSObject Class doesn't work?

I wrote a class contain a collectionview to show in main viewcontroller,
but it was "never" shows the cell data. And the cell background color wasn't change to black..(change color is only for test, not my purpose)..
(The collectionView can be showed correctly)
Where I should to correct it?
class CellClass: NSObject,
UICollectionViewDataSource,
UICollectionViewDelegate,
UICollectionViewDelegateFlowLayout
{
let cellid = "cellid"
let cv : UICollectionView = {
let fl = UICollectionViewFlowLayout()
let v = UICollectionView(frame: .zero,
collectionViewLayout: fl)
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.white
return v
}()
override init() {
super.init()
print("init")
cv.delegate = self
cv.dataSource = self
cv.register(Cell.self,
forCellWithReuseIdentifier: cellid)
if let window = UIApplication.shared.keyWindow {
window.addSubview(cv)
cv.frame = CGRect(x: 0,
y: window.frame.height - 300,
width: window.frame.width,
height: 300)
}
}
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
print("numberOfItemsInSection")
return 3
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("cell")
let vc = collectionView.dequeueReusableCell(withReuseIdentifier: cellid,
for: indexPath) as! Cell
vc.lbl.text = "test"
vc.backgroundColor = UIColor.black
return vc
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 300, height: 100)
}
}
class Cell: UICollectionViewCell {
let lbl : UILabel = {
let t = UILabel()
t.translatesAutoresizingMaskIntoConstraints = false
return t
} ()
override init(frame: CGRect) {
super.init(frame: frame)
lbl.frame = CGRect(x: 0,
y: 0,
width: 200,
height: 30)
addSubview(lbl)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I could be wrong but I have a feeling you do something like this in your viewDidLoad
let cellClass = CellClass()
As soon as the didLoad function completes its execution, cellClass no longer persists in order to be the datasource and delegate.
You can try this by adding a deinit to your CellClass
deinit {
print("Cell Class Deinit")
}
And I believe it will be called
What I suggest instead is to persist the object beyond viewDidLoad
class AVPlayerScroll: UIViewController, UIGestureRecognizerDelegate
{
var cellClass: CellClass?
override func viewDidLoad()
{
super.viewDidLoad()
cellClass = CellClass()
}
I believe this should give you the results you are looking for
you can try couple of things here.
You can either try to set the constraints programmatically. Something like this.
NSLayoutConstraint.activate([
lbl.leadingAnchor.constraint(equalTo: self.leadingAnchor),
lbl.topAnchor.constraint(equalTo: self.topAnchor),
lbl.widthAnchor.constraint(equalToConstant: 200),
actionSheetView.heightAnchor.constraint(equalToConstant: 30)])
You can try to assign the frame to the UILabel (lbl) subview in layoutSubviews() method.

Make UICollectionViewCells with UIImageView height based on image

I want to make a UICollectionView with the cells the width of the screen, but the height is based on the aspect ratio of the UIImageView inside.
In my current implementation all of the UICollectionViewCell instances are squares and this isn't what I want.
I want to use auto layout somehow to make the size of the image the width of the screen, but the relevant height depends on the aspect ratio of the image used.
Everything is done programatically in code.
My code:
class ViewController: UIViewController {
var data = ["1","2","3","4","5","6","7","8","1","2","3","4","5","6","7","8"]
var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.register(SubclassedCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
self.collectionView.alwaysBounceVertical = true
self.collectionView.backgroundColor = .white
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func loadView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: UIScreen.main.bounds.size.width - 20, height: UIScreen.main.bounds.size.width / 1)
layout.scrollDirection = .vertical
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
self.view = collectionView
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? SubclassedCollectionViewCell {
let data = self.data[indexPath.item]
cell.setupCell(image: data)
return cell
}
fatalError("Could not dequeue cell")
}
}
with the cell
class SubclassedCollectionViewCell: UICollectionViewCell {
var hotelImageView: UIImageView = {
var hotelView = UIImageView()
hotelView.contentMode = .scaleAspectFill
hotelView.clipsToBounds = true
return hotelView
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(hotelImageView)
hotelImageView.translatesAutoresizingMaskIntoConstraints = false
hotelImageView.topAnchor.constraint(equalTo: topAnchor).isActive = true
hotelImageView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
hotelImageView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
hotelImageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupCell(image: String) {
if let image : UIImage = UIImage(named: image) {
hotelImageView.image = image
}
}
}
First, calculate the ratio based on the width and multiply by collection view width you can get a new height based on width.
Final Code: Remove itemSize from the loadview function. And calculate height and return item size in sizeForItemAt method.
loadView
override func loadView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
// layout.itemSize = CGSize(width: UIScreen.main.bounds.size.width - 20, height: UIScreen.main.bounds.size.width / 1) <-- Remove this
layout.scrollDirection = .vertical
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
self.view = collectionView
}
UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? SubclassedCollectionViewCell {
let data = self.data[indexPath.item]
cell.setupCell(image: data)
return cell
}
fatalError("Could not dequeue cell")
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
guard let iamgeData = UIImage(named: self.data[indexPath.item]) else {
return .zero
}
let width = collectionView.bounds.width
let heightOnWidthRatio = iamgeData.size.height / iamgeData.size.width
let height = width * heightOnWidthRatio
return CGSize(width: width, height: height)
}
}

UIcollectionView not updating the size and layout of UIcollectionCell upon phone rotation

I have a UIcollectionView with images. When I rotate the screen, the cells do not resize as expected. I have tried adding collectionView.collectionViewLayout.invalidateLayout() to the viewDidLayoutSubviews() - but this only causes a crash. Please can someone advise?
I have a UIcollectionView with images. When I rotate the screen, the cells do not resize as expected. I have tried adding collectionView.collectionViewLayout.invalidateLayout() to the viewDidLayoutSubviews() - but this only causes a crash. Please can someone advise?
Portrait:
Landdscape:
class GridPicksCollectionViewController: UICollectionViewController{
let cellId = "gridyPickCell"
var numCelPerRow: CGFloat = 3
var layout: UICollectionViewFlowLayout!
let borderInset: CGFloat = 3
let interCellSpacing: CGFloat = 3
let spacingBetweenRows: CGFloat = 3
var dataSource = [UIImage]()
var collectionViewWidth: CGFloat!
override func viewDidLoad() {
super.viewDidLoad()
generalSetup()
setUpDateSource()
setupCollectionViewLayout()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("viewDidLayoutSubviews")
// Update collectionViewWidth upon rotation
collectionViewWidth = self.collectionView.frame.width
//collectionView.collectionViewLayout.invalidateLayout() - causes app to crash
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
print("viewWillTransition")
}
private func generalSetup(){
navigationController?.navigationBar.barTintColor = UIColor.appDarkGreen
navigationItem.title = "Girdy Picks"
let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
navigationController?.navigationBar.titleTextAttributes = textAttributes
collectionView.backgroundColor = UIColor.white
collectionViewWidth = collectionView.frame.width
// Make sure collectionView is always within the safe area layout
if #available(iOS 11.0, *) {
collectionView?.contentInsetAdjustmentBehavior = .always
}
// Register UICollectionViewCell
collectionView.register(GirdyPickCollectionCell.self, forCellWithReuseIdentifier: cellId)
}
private func setUpDateSource(){
let images: [UIImage] = [UIImage.init(named: "buddha")!, UIImage.init(named: "sharpener")!, UIImage.init(named: "cars")!, UIImage.init(named: "houses")!, UIImage.init(named: "houses2")!, UIImage.init(named: "tower1")!, UIImage.init(named: "tower2")!]
dataSource = images
}
private func setupCollectionViewLayout(){
collectionView.delegate = self
collectionView.dataSource = self
layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
layout.minimumInteritemSpacing = interCellSpacing // distance between cells in a row
layout.minimumLineSpacing = spacingBetweenRows // distance in between rows
layout.sectionInset = UIEdgeInsets.init(top: borderInset, left: borderInset, bottom: borderInset, right: borderInset) // border inset for collectionView
}
}
extension GridPicksCollectionViewController: UICollectionViewDelegateFlowLayout{
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSource.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! GirdyPickCollectionCell
cell.imageView.image = dataSource[indexPath.row]
return cell
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedImage = dataSource[indexPath.row]
let showImageVC = ImageGridViewController()
showImageVC.modalPresentationStyle = .fullScreen
showImageVC.imageToDisplay = selectedImage
self.present(showImageVC, animated: true) {}
}
// Determine size for UICollectionCell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
print("sizeForItemAt")
guard let collectionWidth = collectionViewWidth else{ return CGSize.init()}
let widthCell = (collectionWidth - interCellSpacing * 2 - borderInset * 2) / numCelPerRow
return CGSize.init(width: widthCell, height: widthCell)
}
}
You are wrong about your lifecycle of events. When the device is rotated, your view hierarchy becomes notified of this event, and views start to re-layout themselves based on the information they have. After this has been finished, viewDidLayoutSubviews() will be called, not before.
Since you are updating the collectionViewWidth property in this method, the layout still uses the old value when calling collectionView(_:layout:sizeForItemAt:). You need to set this value in viewWillLayoutSubviews().
Alternatively, you can write a setter for this property which will call invalidateLayout() on the collection view's layout, but this will cause an unnecessary layout pass.

CollectionView cells Confused

I am busy with a speech app with a soundboard when you tap on a CollectionView Cell then iPhone will speak the text. I have a little bug in my app and I don't know what the reason is.
I have built a CollectionView with images as backgroundViews. It works but when I go to an other view ,for example to the Paint view, and I will go back then the cells will confuse.
Can anybody tell me what goes wrong and how I solve it?
Thanks!
This is my code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
loadData()
let itemSize = UIScreen.main.bounds.width/2 - 5
let itemHeight = itemSize / 2
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets.init(top: 3, left: 3, bottom: 3, right: 3)
layout.itemSize = CGSize(width: itemSize, height: itemHeight)
layout.minimumInteritemSpacing = 3
layout.minimumLineSpacing = 3
soundboard.collectionViewLayout = layout
navigationItem.rightBarButtonItem = editButtonItem
if(traitCollection.forceTouchCapability == .available){
registerForPreviewing(with: self as UIViewControllerPreviewingDelegate, sourceView: collectionView)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
loadData()
}
func loadData() {
let soundRequest:NSFetchRequest<Soundboard> = Soundboard.fetchRequest()
do {
soundBoardData = try managedObjectContext.fetch(soundRequest)
self.soundboard.reloadData()
} catch {
print("Error")
}
}
// MARK: - Collection View
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return soundBoardData.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> soundboardCellVC {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! soundboardCellVC
let soundItem = soundBoardData[indexPath.row]
if let getCellPhoto = soundItem.photo as Data? {
cell.title.text = "";
let cellPhoto = UIImage(data: getCellPhoto)
let cellPhotoFrame = UIImageView(image:cellPhoto)
cellPhotoFrame.frame = CGRect(x: 0, y: 0, width: cell.frame.width, height: cell.frame.height)
cellPhotoFrame.contentMode = UIView.ContentMode.scaleAspectFill
cell.backgroundView = UIView()
cell.backgroundView!.addSubview(cellPhotoFrame)
}
else
{
cell.title.text = soundItem.title;
cell.backgroundColor = UIColor(red: CGFloat(soundItem.colorRed), green: CGFloat(soundItem.colorGreen), blue: CGFloat(soundItem.colorBlue), alpha: 1)
let fontAwesomeIcon = FAType(rawValue: Helper.FANames.index(of: soundItem.icon!)!)
cell.icon.setFAIconWithName(icon: fontAwesomeIcon!, textColor: UIColor.white)
}
cell.layer.cornerRadius = 10
cell.layer.masksToBounds = true
cell.delegate = self as SoundboardCellDelegate
return cell
}
A good practice when working with cells is to clear any properties that you don't want to persist when the cell gets reused.
https://developer.apple.com/documentation/uikit/uicollectionreusableview/1620141-prepareforreuse
For example in your cells class you can do:
override func prepareForReuse() {
super.prepareForReuse()
self.title.text = nil
}
This keeps your cellForItem method cleaner, and you know for sure that the above actions will have been carried out before you reuse it.
Cells are re-used, which means that you are adding new image views to cells that have existing image views.
You should create a single UIImageView that is in your soundboardCellVC class and simply put the image into that, or set the image to nil as required.
A couple of other style points:
By convention, classes should start with an uppercase letter and it is a cell subclass, not a View Controller subclass, so it should be something like SoundboardCell not soundboardCellVC
Finally, your cellForItemAt signature should match the protocol declaration and be declared as returning a UICollectionViewCell, not your specific cell subclass:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {