I'm want to add the leading and trailing into Imageview . The Imageview properties pass from collection view cell. I have tried following to add the leading and trailing but still the leading and trilling not added into Imageview .
//imageView.leftAnchor.constraint(equalTo: view.leftAnchor , constant: 10),
//imageView.rightAnchor.constraint(equalTo: view.rightAnchor,constant: 10),
imageView.leadingAnchor.constraint(equalToSystemSpacingAfter: view.leadingAnchor, multiplier: 3),
imageView.trailingAnchor.constraint(equalToSystemSpacingAfter: view.trailingAnchor, multiplier: 10),
Here is the view controller with collection view cell:
class PhotoViewController: UIViewController {
var viewModel : PhotoViewModel
init(viewModel : PhotoViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
private let collectionView: UICollectionView = {
let viewLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: viewLayout)
collectionView.backgroundColor = .white
return collectionView
}()
private enum LayoutConstant {
static let spacing: CGFloat = 16.0
static let itemHeight: CGFloat = 230.0
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var activityIndicator: UIActivityIndicatorView = {
let activityIndicator = UIActivityIndicatorView()
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
return activityIndicator
}()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupLayouts()
viewModel.fecthPhotoRecord()
collectionView.delegate = self
collectionView.dataSource = self
viewModel.delegate = self
view.backgroundColor = .lightGray
collectionView.reloadData()
}
private func setupViews() {
view.backgroundColor = .white
view.addSubview(collectionView)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(PhotoCollectionViewCell.self, forCellWithReuseIdentifier: PhotoCollectionViewCell.identifier)
}
private func setupLayouts() {
collectionView.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for `collectionView`
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor),
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor)
])
}
}
extension PhotoViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModel.hits.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoCollectionViewCell.identifier, for: indexPath) as! PhotoCollectionViewCell
let row = indexPath.row
let hits = viewModel.hits[indexPath.row]
cell.configureCell(tagName: hits.tags)
cell.configureImageCell(row: row, viewModel: viewModel)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
let detailVC = PhtotoDetailsViewController()
detailVC.rowSelected = indexPath.row
let navController = UINavigationController(rootViewController: detailVC)
detailVC.photoviewModel = viewModel
// navController.modalPresentationStyle = .fullScreen
present(navController, animated: true, completion: nil)
}
}
extension PhotoViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = itemWidth(for: view.frame.width, spacing: LayoutConstant.spacing)
return CGSize(width: width, height: LayoutConstant.itemHeight)
}
func itemWidth(for width: CGFloat, spacing: CGFloat) -> CGFloat {
let itemsInRow: CGFloat = 2
let totalSpacing: CGFloat = 2 * spacing + (itemsInRow - 1) * spacing
let finalWidth = (width - totalSpacing) / itemsInRow
return floor(finalWidth)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: LayoutConstant.spacing, left: LayoutConstant.spacing, bottom: LayoutConstant.spacing, right: LayoutConstant.spacing)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return LayoutConstant.spacing
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return LayoutConstant.spacing
}
}
Code in collection view cell .
import UIKit
protocol ReusableView: AnyObject {
static var identifier: String { get }
}
class PhotoCollectionViewCell: UICollectionViewCell {
private enum Constants {
// MARK: contentView layout constants
static let contentViewCornerRadius: CGFloat = 4.0
// MARK: profileImageView layout constants
static let imageHeight: CGFloat = 180.0
// MARK: Generic layout constants
static let verticalSpacing: CGFloat = 8.0
static let horizontalPadding: CGFloat = 16.0
static let profileDescriptionVerticalPadding: CGFloat = 8.0
}
private let imageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.contentMode = .scaleAspectFill
return imageView
}()
private let tagLabel: UILabel = {
let label = UILabel(frame: .zero)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
override init(frame: CGRect) {
super.init(frame: .zero)
setupViews()
setupLayouts()
}
private func setupViews() {
contentView.clipsToBounds = true
contentView.layer.cornerRadius = Constants.contentViewCornerRadius
contentView.backgroundColor = .white
contentView.addSubview(imageView)
contentView.addSubview(tagLabel)
}
private func setupLayouts() {
imageView.translatesAutoresizingMaskIntoConstraints = false
tagLabel.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for `imageView`
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
imageView.topAnchor.constraint(equalTo: contentView.topAnchor),
imageView.heightAnchor.constraint(equalToConstant: Constants.imageHeight)
])
// Layout constraints for `tagLabel`
NSLayoutConstraint.activate([
tagLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Constants.horizontalPadding),
tagLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -Constants.horizontalPadding),
tagLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: Constants.profileDescriptionVerticalPadding)
])
}
func configureCell(tagName: String) {
tagLabel.text = tagName
}
func configureImageCell(row: Int, viewModel: PhotoViewModel) {
imageView.image = nil
viewModel
.downloadImage(row: row) { [weak self] data in
let image = UIImage(data: data)
self?.imageView.image = image
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension PhotoCollectionViewCell: ReusableView {
static var identifier: String {
return String(describing: self)
}
}
Here is the details view controller:
import UIKit
class PhtotoDetailsViewController: UIViewController {
var photoviewModel : PhotoViewModel?
var peopleDetailsViewModel:PeopleDetailsViewModel?
private let imageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.contentMode = .scaleAspectFill
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
private let tagLabel: UILabel = {
let label = UILabel(frame: .zero)
label.textAlignment = .center
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(imageView)
view.addSubview(tagLabel)
setUpUI()
setPhoto()
setContrain()
}
private func setContrain(){
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: view.topAnchor,constant: 100),
imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor,constant: -200),
//imageView.leftAnchor.constraint(equalTo: view.leftAnchor , constant: 10),
//imageView.rightAnchor.constraint(equalTo: view.rightAnchor,constant: 10),
imageView.leadingAnchor.constraint(equalToSystemSpacingAfter: view.leadingAnchor, multiplier: 3),
imageView.trailingAnchor.constraint(equalToSystemSpacingAfter: view.trailingAnchor, multiplier: 10),
tagLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor),
tagLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor),
tagLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
}
private func setUpUI(){
tagLabel.text = photoviewModel?.hits.first?.tags
}
var rowSelected = 0
private func setPhoto(){
photoviewModel?.downloadImage(row: rowSelected) { [weak self] data in
DispatchQueue.main.async {
let image = UIImage(data: data)
self?.imageView.image = image
}
}
}
}
Debugging results in
Here is the screenshot:
Leading added .
Related
I have collection view that holds a cell. I am resizing that cell in sizeForItemAt function. But the problem is , the cell is centre aligned if the height is small. I want to align all the resized cells to the bottom of collection view.
I am using Swift. I tried using constraints on the view by adding the view programatically. No results. I am trying to make a view similar to a scrollable design in Canva App.
Please help.
My collection view looks like this currently Simulator Image and i want to align the cell to the bottom always like canva view that i am trying to make
Code:
CollectionViewController -
import UIKit
class CollectionViewController: UIViewController,UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionView: UICollectionView!
let columns: CGFloat = 6.0
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(OneCollectionViewCell.self, forCellWithReuseIdentifier: "cellOne")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Int(columns)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellOne", for: indexPath) as! OneCollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width : CGFloat
let height : CGFloat
if indexPath.item == 0 {
width = 100
height = 50
} else if indexPath.item == 1{
width = 80
height = 100
} else if indexPath.item == 2 {
width = 50
height = 70
}else {
width = 80
height = 100
}
return CGSize(width: width, height: height)
}
}
CollectionViewCell-
import UIKit
class OneCollectionViewCell: UICollectionViewCell {
public var view1: UIView = {
let viewView = UIView()
viewView.translatesAutoresizingMaskIntoConstraints = false
viewView.contentMode = .scaleToFill
viewView.clipsToBounds = true
return viewView
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(view1)
view1.backgroundColor = .black
view1.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0).isActive = true
view1.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0).isActive = true
view1.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10).isActive = true
view1.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 5).isActive = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//fatalError("init(coder:) has not been implemented")
}
}
class BottomAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
override init() {
super.init()
self.scrollDirection = .horizontal
// self.minimumInteritemSpacing = 10.0
// self.minimumLineSpacing = 10.0
self.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)?
.map { $0.copy() } as? [UICollectionViewLayoutAttributes]
attributes?
.reduce([CGFloat: (CGFloat, [UICollectionViewLayoutAttributes])]()) {
guard $1.representedElementCategory == .cell else { return $0 }
return $0.merging([ceil($1.center.y): ($1.frame.origin.y, [$1])]) {
($0.0 < $1.0 ? $0.0 : $1.0, $0.1 + $1.1)
}
}
.values.forEach { minY, line in
line.forEach {
$0.frame = $0.frame.offsetBy(
dx: 0,
dy: minY + $0.frame.origin.y
)
}
}
return attributes
}
}
In viewDidLoad
collectionView.collectionViewLayout = BottomAlignedCollectionViewFlowLayout()
Code:
CollectionViewController -
class CollectionViewController: UIViewController, UICollectionViewDelegate,
UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionView: UICollectionView!
let columns: CGFloat = 6.0
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(OneCollectionViewCell.self, forCellWithReuseIdentifier: "cellOne")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Int(columns)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellOne", for: indexPath) as! OneCollectionViewCell
let height : CGFloat
if indexPath.item == 0 {
height = 50
} else if indexPath.item == 1{
height = 100
} else if indexPath.item == 2 {
height = 70
}else {
height = 100
}
cell.configCell(height: height)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width : CGFloat
let height : CGFloat = 100
if indexPath.item == 0 {
width = 100
}
else if indexPath.item == 1{
width = 100
} else if indexPath.item == 2 {
width = 50
}else {
width = 80
}
return CGSize(width: width, height: height)
}
}
CollectionViewCell-
import UIKit
class OneCollectionViewCell: UICollectionViewCell {
public var view1: UIView = {
let viewView = UIView()
viewView.translatesAutoresizingMaskIntoConstraints = false
viewView.contentMode = .scaleToFill
viewView.clipsToBounds = true
return viewView
}()
var cstHeightAnchor: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(view1)
view1.backgroundColor = .black
cstHeightAnchor = view1.heightAnchor.constraint(equalToConstant: 100)
cstHeightAnchor.isActive = true
view1.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0).isActive = true
view1.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10).isActive = true
view1.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 5).isActive = true
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//fatalError("init(coder:) has not been implemented")
}
func configCell(height: CGFloat) {
cstHeightAnchor.constant = height
}
}
I'm trying to make a news feed like app using UICollectionView with adaptive cell height. I found this tutorial and used the option "2. Solution for iOS 11+" for my code, which works perfectly fine. However, whenever I try to add more subviews to the cell and layout them as required, I get this error: "Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want."
Below is my code which gives me an error.
NewsFeedViewController:
import UIKit
class NewsFeedViewController: UIViewController {
var friends: [Friend] = []
var allPosts: [Post?] = []
var shuffledPosts: [Post?] = []
var collectionView: UICollectionView = {
let layout = NewsFeedFlowLayout()
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize // new
layout.scrollDirection = .vertical
let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
collection.backgroundColor = UIColor.systemRed
collection.isScrollEnabled = true
collection.contentInsetAdjustmentBehavior = .always
collection.translatesAutoresizingMaskIntoConstraints = false
return collection
}()
let cellID = "NewsFeedCollectionViewCell"
override func viewDidLoad() {
super.viewDidLoad()
getPosts()
view.addSubview(collectionView)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(NewsFeedCollectionViewCell.self, forCellWithReuseIdentifier: cellID)
collectionView.pin(to: view)
}
func getPosts() {
friends = FriendFactory().friends
allPosts = friends.flatMap{$0.posts}
var tempPosts = allPosts
for _ in allPosts {
if let index = tempPosts.indices.randomElement() {
let post = tempPosts[index]
shuffledPosts.append(post)
tempPosts.remove(at: index)
}
}
}
}
extension NewsFeedViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return shuffledPosts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! NewsFeedCollectionViewCell
let post = shuffledPosts[indexPath.row]
let friend = friends.first(where: {$0.identifier == post?.authorID})
let text = post?.postText
cell.configurePostText(postText: text!)
return cell
}
}
NewsFeedCollectionViewCell:
import UIKit
class NewsFeedCollectionViewCell: UICollectionViewCell {
var selectedPost = Post()
var likeBarView: LikeBarView = {
let view = LikeBarView()
view.backgroundColor = .systemIndigo
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var postTextLabel: UILabel = {
let label = UILabel()
label.font = label.font.withSize(20)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.backgroundColor = .systemYellow
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addViews()
setupConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addViews() {
addSubview(likeBarView)
addSubview(postTextLabel)
}
func configurePostText(postText: String) {
postTextLabel.text = postText
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
layoutIfNeeded()
layoutAttributes.frame.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
return layoutAttributes
}
func setupConstraints() {
NSLayoutConstraint.activate([
likeBarView.topAnchor.constraint(equalTo: topAnchor, constant: 20),
likeBarView.leadingAnchor.constraint(equalTo: leadingAnchor),
likeBarView.trailingAnchor.constraint(equalTo: trailingAnchor),
likeBarView.heightAnchor.constraint(equalToConstant: 100),
postTextLabel.topAnchor.constraint(equalTo: likeBarView.bottomAnchor, constant: 20),
postTextLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
postTextLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
postTextLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
])
}
}
NewsFeedFlowLayout:
import UIKit
final class NewsFeedFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let layoutAttributesObjects = super.layoutAttributesForElements(in: rect)?.map{ $0.copy() } as? [UICollectionViewLayoutAttributes]
layoutAttributesObjects?.forEach({ layoutAttributes in
if layoutAttributes.representedElementCategory == .cell {
if let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame {
layoutAttributes.frame = newFrame
}
}
})
return layoutAttributesObjects
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let collectionView = collectionView else {
fatalError()
}
guard let layoutAttributes = super.layoutAttributesForItem(at: indexPath)?.copy() as? UICollectionViewLayoutAttributes else {
return nil
}
layoutAttributes.frame.origin.x = sectionInset.left
layoutAttributes.frame.size.width = collectionView.safeAreaLayoutGuide.layoutFrame.width - sectionInset.left - sectionInset.right
return layoutAttributes
}
}
I enclose the screenshot of the collection view I'm getting and the screenshot of the analysis of the error done here, which is saying that the height of the cell is not dynamic if I'm getting it right.
What am I doing wrong?
Adding this line of code to NewsFeedViewController silenced the error:
layout.estimatedItemSize = CGSize(width: 375, height: 200)
enter image description hereI make the word game, I have a word, this word needs to be divided into characters and each character should be added to a separate cell and there would be a space between the words, while using a custom class for the label. To do this, I use collection viewcells, I could not add a character to each cell, I was only able to transfer a whole word to each cell. Please help solve this problem.
P.S I added screenshots as it should be and as it isenter image description here
My code
ViewController.swift
import UIKit
class ViewController: UIViewController {
let cellID = "Cell"
let word = "Hello My People"
var index = 0
var targets = [TargetView]()
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 12
layout.minimumInteritemSpacing = 3
let collectionView1 = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView1.backgroundColor = .red
collectionView1.translatesAutoresizingMaskIntoConstraints = false
return collectionView1
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .brown
view.addSubview(collectionView)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(CharCell.self, forCellWithReuseIdentifier: cellID)
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16).isActive = true
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 400).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -70).isActive = true
//var anagram1 = word.count
}
}
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//
// var anagram1 = word
// //var leg = anagram1.count
////
//// //targets = []
// for (index,letter) in anagram1.enumerated() {
// let target = TargetView(letter: letter, sideLength: 15)
// if letter != " " {
// // let target = TargetView(letter: letter, sideLength: 15)
//// target.center = CGPoint(x: xOffset + CGFloat(index) /* * 20 */ * (15 + tileMargin) - view.frame.minX, y: UIScreen.main.bounds.size.height - 100) //100 //UIScreen.main.bounds.size.height - CGFloat(index) * 50
////
//// view.addSubview(target)
////
// //targets.append(target)
// //stackView.addArrangedSubview(target)
// targets.append(target)
// return 15
// }
// }
if index < word.count {
return word.count
} else {
return 0
}
//return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! CharCell
for (index,letter) in word.enumerated() {
let target = TargetView(letter: letter, sideLength: 15)
if letter != " " {
// let target = TargetView(letter: letter, sideLength: 15)
targets.append(target)
cell.label.text = String(letter)
}
}
cell.label.text = word
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 30, height: 30)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 15, left: 0, bottom: 0, right: 0)
}
}
CharCell.swift
import UIKit
class CharCell: UICollectionViewCell {
var label1 = [TargetView]()
lazy var label: UILabel = {
let label = UILabel()
label.backgroundColor = .cyan
label.textAlignment = .left
label.font = .boldSystemFont(ofSize: 10)
label.text = "_"//String(letter).uppercased()
label.textColor = .black
label.numberOfLines = 3
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupCell()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupCell() {
backgroundColor = .white
label.frame = CGRect(x: 0, y: 1, width: frame.width , height: frame.height )
self.addSubview(label)
}
}
TargetView.swift
import UIKit
class TargetView: UILabel {
var letter: Character!
var isMatch = false
init(letter: Character, sideLength: CGFloat) {
self.letter = letter
//let image = UIImage(named: "slot")
//super.init(image: image)
//let scale = CGRect(x: 0, y: 0, width: (image?.size.width)! * scale, height: (image?.size.height)! * scale)
super.init(frame: CGRect(x: 0, y: 0, width: 15, height: 30))
self.backgroundColor = .red
self.textAlignment = .center
self.font = .boldSystemFont(ofSize: 60.0 / 3)
self.text = "_"//String(letter).uppercased()
self.textColor = .white
self.lineBreakMode = .byWordWrapping
self.adjustsFontSizeToFitWidth = true
self.translatesAutoresizingMaskIntoConstraints = false
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The problem is you’re setting cell.label.text to word in every cell. Just split the phrase into components and add them to an array. In my example, I simplified it.
You'll likely need to adapt it to your app, but here's a quick implementation just to get you going.
import UIKit
class ViewController: UIViewController {
let cellID = "Cell"
let word = "Hello My People"
var arr = [String]()
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 12
layout.minimumInteritemSpacing = 3
let collectionView1 = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView1.backgroundColor = .gray
collectionView1.translatesAutoresizingMaskIntoConstraints = false
return collectionView1
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.backgroundColor = .brown
view.addSubview(collectionView)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellID)
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16).isActive = true
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 400).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -70).isActive = true
for char in word {
arr.append(String(char))
}
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arr.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath)
cell.backgroundColor = .red
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
let cellText = arr[indexPath.item]
lbl.text = cellText
if cellText == " " {
cell.backgroundColor = .clear
}
cell.addSubview(lbl)
lbl.centerXAnchor.constraint(equalTo: cell.centerXAnchor).isActive = true
lbl.centerYAnchor.constraint(equalTo: cell.centerYAnchor).isActive = true
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 35, height: 35)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 15, left: 0, bottom: 0, right: 0)
}
}
Result:
I'm trying to create an UICollectionView which can scroll horizontally and vertically.
Here are my codes:
Here is the Model:
import UIKit
class AppCategory: NSObject {
var name: String?
var apps: [App]?
static func sampleAppCategories() -> [AppCategory] {
// Chapter 1
let chapter1 = AppCategory()
chapter1.name = NSLocalizedString("CHAPTER 1: ", comment: "1") + NSLocalizedString("19 Sections", comment: "title")
var apps1 = [App]()
let chapter1App = App()
chapter1App.imageName = "IMG_2487"
let chapter11App = App()
chapter11App.imageName = "IMG_2502"
let chapter12App = App()
chapter12App.imageName = "IMG_2507"
apps1.append(chapter1App)
apps1.append(chapter11App)
apps1.append(chapter12App)
chapter1.apps = apps1
// Chapter 2
let chapter2 = AppCategory()
chapter2.name = NSLocalizedString("CHAPTER 2: ", comment: "2") + NSLocalizedString("19 Sections", comment: "title")
var apps2 = [App]()
let chapter2App = App()
chapter2App.imageName = "IMG_2508"
apps2.append(chapter2App)
chapter2.apps = apps2
// Chapter 3
let chapter3 = AppCategory()
chapter3.name = NSLocalizedString("CHAPTER 3: ", comment: "title") + NSLocalizedString("19 Sections", comment: "title")
var apps3 = [App]()
let chapter3App = App()
chapter3App.imageName = "IMG_2510"
apps3.append(chapter3App)
chapter3.apps = apps3
// Chapter 4
let chapter4 = AppCategory()
chapter4.name = NSLocalizedString("CHAPTER 4: ", comment: "title") + NSLocalizedString("19 Sections", comment: "title")
var apps4 = [App]()
let chapter4App = App()
chapter4App.imageName = "IMG_2511"
apps4.append(chapter4App)
chapter4.apps = apps4
return [chapter1, chapter2, chapter3, chapter4]
}
}
class App: NSObject {
var imageName: String?
}
Here is the FeatureViewController:
import UIKit
class FeaturedViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var appCategories: [AppCategory]?
let verticalCellId = "verticalCellId"
let horizontalCellId = "horizontalCellId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = .white
appCategories = AppCategory.sampleAppCategories()
navigationItem.title = NSLocalizedString("Original", comment: "Original")
navigationController?.navigationBar.prefersLargeTitles = true
collectionView?.register(FeaturedVerticalCell.self, forCellWithReuseIdentifier: verticalCellId)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = appCategories?.count {
return count
}
return 0
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: verticalCellId, for: indexPath) as! FeaturedVerticalCell
cell.appCategory = appCategories?[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
switch UIDevice.current.userInterfaceIdiom {
case .phone:
collectionView.reloadData()
return CGSize(width:view.frame.width, height: view.frame.width / 5 * 4 )
case .pad:
let padding: CGFloat = 50
let collectionViewSize = collectionView.frame.size.width - padding
collectionView.reloadData()
return CGSize(width: collectionViewSize / 5 * 4, height: collectionViewSize / 5 * 3 )
case .tv:
break
case .carPlay:
break
case .unspecified:
break
}
return CGSize(width: 0, height: 0)
}
}
Here is the FeaturedVerticalCell:
import UIKit
struct Titles {
var title: String?
var images:[String]
}
class FeaturedVerticalCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let horizontalCellId = "horizontalCellId"
var appCategory: AppCategory? {
didSet {
if let name = appCategory?.name {
titleLabel.text = name
}
}
}
let horizontalCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.clear
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
let titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
if UIDevice.current.userInterfaceIdiom == .phone {
label.font = UIFont.systemFont(ofSize: 14.0, weight: UIFont.Weight.medium)
} else {
label.font = UIFont.systemFont(ofSize: 20.0, weight: UIFont.Weight.medium)
}
label.textAlignment = .left
label.textColor = UIColor.darkGray
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
horizontalCollectionView.dataSource = self
horizontalCollectionView.delegate = self
horizontalCollectionView.register(HorizontalCollectionViewCell.self, forCellWithReuseIdentifier: horizontalCellId)
addSubview(horizontalCollectionView)
horizontalCollectionView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 24).isActive = true
horizontalCollectionView.topAnchor.constraint(equalTo: self.topAnchor, constant: 36).isActive = true
horizontalCollectionView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -24).isActive = true
horizontalCollectionView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 8).isActive = true
addSubview(titleLabel)
titleLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 24).isActive = true
titleLabel.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -24).isActive = true
titleLabel.bottomAnchor.constraint(equalTo: horizontalCollectionView.topAnchor, constant: 0).isActive = true
titleLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 24).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = appCategory?.apps?.count {
return count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: horizontalCellId, for: indexPath) as! HorizontalCollectionViewCell
cell.app = appCategory?.apps?[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.height * 4 / 5, height: frame.height * 4 / 5)
}
}
class HorizontalCollectionViewCell: UICollectionViewCell {
var app: App? {
didSet {
if let imageName = app?.imageName {
photoImageView.image = UIImage(named: imageName)
}
}
}
let photoImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.layer.cornerRadius = 10
iv.image = #imageLiteral(resourceName: "IMG_2545")
iv.layer.masksToBounds = true
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(photoImageView)
photoImageView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0).isActive = true
photoImageView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0).isActive = true
photoImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
photoImageView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
But now I got a problem: the rows in horizontal sections will change somehow when I scroll vertically. Any way to get it back to work normally?
Don't know what's wrong there on earth, but finally I got it work. Here you go:
import UIKit
class FeaturedViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
print("SeachController tapped.")
}
let firstCellId = "cellfirstCellIdId"
var appCategories: [AppCategory]?
override func viewDidLoad() {
super.viewDidLoad()
appCategories = AppCategory.sampleAppCategories()
collectionView?.backgroundColor = UIColor.clear
collectionView?.register(CategoryCell.self, forCellWithReuseIdentifier: firstCellId)
navigationItem.title = NSLocalizedString("Original", comment: "Original")
navigationController?.navigationBar.prefersLargeTitles = true
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
navigationItem.hidesSearchBarWhenScrolling = true
self.navigationItem.searchController = searchController
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: firstCellId, for: indexPath) as! CategoryCell
cell.appCategory = appCategories?[indexPath.item]
return cell
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = appCategories?.count {
return count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 300)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
Here is the cell:
import UIKit
class CategoryCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var appCategory: AppCategory? {
didSet {
if let name = appCategory?.name {
firstChapterLabel.text = name
}
}
}
let secondCellId = "secondCellId"
let appsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.clear
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
let firstChapterLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
if UIDevice.current.userInterfaceIdiom == .phone {
label.font = UIFont.systemFont(ofSize: 14.0, weight: UIFont.Weight.medium)
} else {
label.font = UIFont.systemFont(ofSize: 20.0, weight: UIFont.Weight.medium)
}
label.textAlignment = .left
label.textColor = UIColor.darkGray
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(appsCollectionView)
appsCollectionView.dataSource = self
appsCollectionView.delegate = self
appsCollectionView.register(AppCell.self, forCellWithReuseIdentifier: secondCellId)
appsCollectionView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 16).isActive = true
appsCollectionView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -16).isActive = true
appsCollectionView.topAnchor.constraint(equalTo: self.topAnchor, constant: 50).isActive = true
appsCollectionView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
addSubview(firstChapterLabel)
firstChapterLabel.leftAnchor.constraint(equalTo: appsCollectionView.leftAnchor, constant: 16).isActive = true
firstChapterLabel.rightAnchor.constraint(equalTo: appsCollectionView.rightAnchor, constant: -16).isActive = true
firstChapterLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 24).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let count = appCategory?.apps?.count {
return count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: secondCellId, for: indexPath) as! AppCell
cell.app = appCategory?.apps?[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.height, height: frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
class AppCell: UICollectionViewCell {
var app: App? {
didSet {
if let imageName = app?.imageName {
photoImageView.image = UIImage(named: imageName)
}
}
}
let photoImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.layer.cornerRadius = 9
iv.layer.masksToBounds = true
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(photoImageView)
photoImageView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 16).isActive = true
photoImageView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -16).isActive = true
photoImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -36).isActive = true
photoImageView.topAnchor.constraint(equalTo: self.topAnchor, constant: 36).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And the model stays as it was described in the question.
I am experiencing some layout issues with collectionViewCells. I am programmatically applying layout constraints and UICollectionViewCells are not being pinned to the 0,0 position in my collectionView. See attached screenshot for reference. Thanks in advance!
class CurrentUserPlaceDetailsVC: UIViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
setupMenuBar()
}
let menuBar: MenuBar = {
let mb = MenuBar()
return mb
}()
private func setupMenuBar() {
view.addSubview(menuBar)
view.addConstraintsWithFormat("H:|[v0]|", views: menuBar)
view.addConstraintsWithFormat("V:|-64-[v0(150)]", views: menuBar)
}
var placesTableVC: PlacesTableVC?
}
class MenuBar: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = UIColor.green
cv.dataSource = self
cv.delegate = self
return cv
}()
let cellId = "cellId"
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
addConstraintsWithFormat("H:|[v0]|", views: collectionView)
addConstraintsWithFormat("V:|[v0]|", views: collectionView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
cell.backgroundColor = .blue
return cell
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension UIView {
func addConstraintsWithFormat(_ format: String, views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
view.translatesAutoresizingMaskIntoConstraints = false
viewsDictionary[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
You need to set the minimumLineSpacing, headerReferenceSize, and/or sectionInset of the UICollectionViewFlowLayout instance you use to initialize your UICollectionView. You can also set the contentInset of the UICollectionView.
Try this:
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.headerReferenceSize = .zero
layout.sectionInset = .zero
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .green
cv.dataSource = self
cv.delegate = self
cv.contentInset = .zero
return cv
}()