Related
enter code hereI'm creating an application that requires a UICollectionView inside a UICollectionViewCell. I've successfully added the UICollectionView and all of its delegate methods within the cell and, on initial load, everything loads correctly. However, as soon as the user attempts to scroll further down in the main UICollectionView, the cells which held the inner UICollectionView, essentially, loose their view. An interesting detail to note is that the issue only rears its head when the user scrolls at a non-snail rate. If the scroll slow enough, the cells will load correctly. Otherwise, the cells will become malformed.
Unfortunately, I've been dealing with this problem for quite a while, as in close to/more than a month at this point. I've tried just about every solution proposed on StackOverflow, Medium articles, YouTube tutorials, etc. but have yet to find the working solution.
This behavior is not exhibited without the conditional rendering if I always add the UICollectionView as a subview.
An important detail to note is that my cells are vertically self-sizing, and every cell is not guaranteed to contain an inner UICollectionView. I suspect part of my issue is born from my handling of this conditional rendering (i.e. if the cell's datasource has images, add the UICollectionView as a subview. Otherwise, don't add it at all).
Additionally, I'm using imported assets in my Xcode project as images. I'm not making any async calls that otherwise may attribute to the images not being fetched and the datasource having 0 images when it should have > 0. My logs in my parseDatasource(withDatasource datasource: (String, [UIImage]) function are showing that datasource.1 contains an array of images in the cells that should have images, as expected. I'm beyond stumped at this point and a bit more than slightly frustrated.
My main UIViewController controlling the cells:
class ExperimentalViewController: UIViewController {
private let cellReuseId = "cellReuseId"
fileprivate var data = [(String, [UIImage])]()
fileprivate let strings = [<a large array of strings>]
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
collection.backgroundColor = .clear
collection.alwaysBounceVertical = true
collection.contentInsetAdjustmentBehavior = .always
return collection
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupDummyData()
setupCollectionView()
}
private func setupDummyData() {
for (i, str) in strings.enumerated() {
var images = [UIImage]()
if i % 2 == 0 {
images = [UIImage(named: "boxed-water-is-better-1464052-unsplash")!, UIImage(named: "boxed-water-is-better-1464052-unsplash")!, UIImage(named: "boxed-water-is-better-1464052-unsplash")!]
}
data.append((str, images))
}
}
private func setupCollectionView() {
if #available(iOS 13.0, *) {
let size = NSCollectionLayoutSize(
widthDimension: NSCollectionLayoutDimension.fractionalWidth(1),
heightDimension: NSCollectionLayoutDimension.estimated(440)
)
let item = NSCollectionLayoutItem(layoutSize: size)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 8)
section.interGroupSpacing = 5
let layout = UICollectionViewCompositionalLayout(section: section)
collectionView.collectionViewLayout = layout
} else {
let layout = ExperimentalFlowLayout()
collectionView.collectionViewLayout = layout
}
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(ExperimentalCell.self, forCellWithReuseIdentifier: cellReuseId)
view.addSubview(collectionView)
collectionView.anchor(top: view.topAnchor, leading: view.leadingAnchor, bottom: view.bottomAnchor, trailing: view.trailingAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}
}
extension ExperimentalViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseId, for: indexPath) as! ExperimentalCell
cell.index = indexPath
cell.backgroundColor = .yellow
cell.setupViews(withDatasource: data[indexPath.item])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 20
}
}
My UICollectionViewCell subclass
class ExperimentalCell: UICollectionViewCell {
// https://stackoverflow.com/questions/37782659/swift-ios-uicollectionview-images-mixed-up-after-fast-scroll/37784212
// http://www.thomashanning.com/the-most-common-mistake-in-using-uitableview/
private let imageCellReuseId = "imageCellReuseId"
var index: IndexPath?
var images = [UIImage]()
private let titleLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
return label
}()
private let label: UILabel = {
let label = UILabel()
label.numberOfLines = 0
return label
}()
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
collection.backgroundColor = .clear
return collection
}()
override init(frame: CGRect) {
super.init(frame: frame)
print("RYANLOG \(index?.row) INIT")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
images = []
imageView.image = nil
collectionView.delegate = nil
collectionView.dataSource = nil
titleLabel.text = nil
label.text = nil
setupViews(withDatasource: nil)
}
func setupViews(withDatasource datasource: (String, [UIImage])?) {
if datasource != nil {
parseDatasource(datasource!)
}
contentView.addSubview(titleLabel)
titleLabel.anchor(top: contentView.topAnchor, leading: contentView.leadingAnchor, bottom: nil, trailing: contentView.trailingAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
contentView.addSubview(label)
if images.count > 0 {
titleLabel.text = "Images"
label.text = String(images.count)
label.anchor(top: titleLabel.bottomAnchor, leading: contentView.leadingAnchor, bottom: nil, trailing: contentView.trailingAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
setupCollectionView()
contentView.addSubview(collectionView)
collectionView.anchor(top: label.bottomAnchor, leading: contentView.leadingAnchor, bottom: contentView.bottomAnchor, trailing: contentView.trailingAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 200)
} else {
titleLabel.text = "No Images"
label.anchor(top: titleLabel.bottomAnchor, leading: contentView.leadingAnchor, bottom: contentView.bottomAnchor, trailing: contentView.trailingAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}
}
private func parseDatasource(_ datasource: (String, [UIImage])) {
label.text = datasource.0
images = datasource.1
print("RYANLOG \(index?.row) Images:", images.count)
}
// MARK: - Used for self-sizing on <= iOS 12
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
layoutIfNeeded()
let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
layoutAttributes.bounds.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
return layoutAttributes
}
}
extension ExperimentalCell: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
fileprivate func setupCollectionView() {
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(ScrollableImageView.self, forCellWithReuseIdentifier: imageCellReuseId)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: imageCellReuseId, for: indexPath) as! ScrollableImageView
cell.imageView.image = images[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 200, height: 200)
}
}
Please excuse the use of print("RYANLOG ...)s. I've been using them for debugging and figured they might be helpful in displaying the areas I believe might be part of the issue.
ScrollableImageView is a subclass of UIView. If you think seeing the code contained within this class would be helpful, please let me know!
Any help is greatly appreciated!
A gif demonstrating the flawed behavior:
https://imgur.com/a/btAXPca
Update: I finally solved this using two different reuse identifiers: "cellRuseId" and "imageCellReuseId". I'm still not sure whether this is the correct solution, but, now my collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) looks like so:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let reuseId = data[indexPath.item].1.count > 0 ? imageCellReuseId : cellReuseId
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseId, for: indexPath) as! ExperimentalCell
cell.index = indexPath
cell.backgroundColor = .yellow
cell.setupViews(withDatasource: data[indexPath.item])
return cell
}
My collection view scrolls correctly and is properly reusing the cells. I won't accept this answer as it's not the most elegant (especially if you have multiple variations of the same cell) so if someone has a better answer, please give a response!
Good afternoon all my app is currently dropping frames and i am not sure how to fix it...im a bit lost.
hopefully you guys/gals can point me in the right direction.
I am currently using Kingfisher to download the images from my backend, everything works well with the exception of the scrolling it's a bit choppy when you scroll down.
the controller code is as followed maybe there's something wrong with it.
i am using storyboard and have removed and added the constraints...but ive had no luck.
import UIKit
import Kingfisher
class WallpaperCVC: UICollectionViewController , UIViewControllerTransitioningDelegate {
var categoryId: String!
var categoryName: String!
var wallpaperBackend: Backend!
var result:CGSize!
// Header peralax
fileprivate let headerId = "headerId"
fileprivate let padding: CGFloat = 16
#IBOutlet var wpMain: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.title = self.categoryName // Name to the controller or section selected
// go go gadget data...
getData()
buttonGray()
setupCollectionViewLayout()
setupCollectionView()
}
func getData(){
GLOBAL_BACKEND_DATA.removeAll()
self.wallpaperBackend = Backend()
self.wallpaperBackend.getWallByIdData(id: self.categoryId) { (god) in
for post in god {
let postData = post as! NSDictionary
let wallpaperCVCId = postData.value(forKey: "id") as! String
let wallpaperCVCFile = postData.value(forKey: "file") as! String
let wallpaperCVCCategory = postData.value(forKey: "category") as! String
let wallpaperCVCDownload = Int(postData.value(forKey: "download") as! String)
GLOBAL_BACKEND_DATA.append(WallpaperModel(wallpaperModelId: wallpaperCVCId, wallpaperModelFile: wallpaperCVCFile, wallpaperModelCategoryId: wallpaperCVCCategory, wallpaperModelDownload: wallpaperCVCDownload!))
}
self.collectionView?.reloadData()
}
}
#IBAction func pressedAction(_ sender: UIButton) {
// do your stuff here
self.dismiss(animated: true, completion: nil)
print("you clicked on button \(sender.tag)")
}
func buttonGray(){
let myButton = UIButton() // if you want to set the type use like UIButton(type: .RoundedRect) or UIButton(type: .Custom)
let img = UIImage(named: "buttonA")
myButton.setImage(img, for: .normal)
// myButton.setTitleColor(UIColor.blue, for: .normal)
myButton.frame = CGRect(x: 15, y: 50, width: 100, height: 100)
// shadow
myButton.layer.shadowOpacity = 0.5
myButton.layer.shadowRadius = 4
myButton.layer.shadowOffset = CGSize(width: 0, height: 5)
myButton.addTarget(self, action: #selector(pressedAction(_:)), for: .touchUpInside)
// haptic feedback
myButton.isHaptic = true
myButton.hapticType = .impact(.medium)
//floatbutton like this
view.addSubview(myButton)
myButton.translatesAutoresizingMaskIntoConstraints = false
myButton.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 10).isActive = true
myButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// You only need one section for now
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// Here call the wallpapers
return GLOBAL_BACKEND_DATA.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// pulling & going to the controller
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "wallpapers", for: indexPath) as! WallpaperCVCell
let godData = GLOBAL_BACKEND_DATA[indexPath.row]
let imageURL = String(format:"%#uploads/image/%#", BASE_BACKEND_URL,godData.wallpaperModelFile) // calling wallpaperModel controller
let escapedURL = imageURL.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
let urls = URL(string:escapedURL!)
// if nothing load this...
let loadImage = UIImage(named: "skullLoading")
cell.collWallpaperImage.kf.setImage(with: urls, placeholder: loadImage)
cell.collWallpaperImage.kf.indicatorType = .activity // Showing a loading indicator while downloading
// lets count the downloads
let count = godData.wallpaperModelDownload as Int // calling wallpaperModel controller
cell.collectionCount.text = String(count) //collection view cell count label
cell.configure() // calling a function from the cell
return cell
}
// Lets move over to the next controller
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Tapped....Tapped go to the next controller..")
collectionView.deselectItem(at: indexPath, animated: true)
let vc = self.storyboard!.instantiateViewController(withIdentifier: "page") as! PageViewController
vc.position = indexPath.row
vc.categoryName = self.categoryName
vc.transitioningDelegate = self
vc.modalPresentationStyle = .custom
self.present(vc, animated: true, completion: nil)
}
}
// MARK: - CollectionView Delegate
extension WallpaperCVC {
// size images
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
result = CGSize(width: 187, height: 314)
if DeviceType.IS_IPHONE_5 {
result = CGSize(width: 160, height: 280)
}
if DeviceType.IS_IPHONE_6 {
result = CGSize(width: 187, height: 314)
}
if DeviceType.IS_IPHONE_6P {
result = CGSize(width: 207, height: 320)
}
if DeviceType.IS_IPHONE_4_OR_LESS {
result = CGSize(width: 151, height: 200)
}
return result;
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 15, left: 8, bottom: 5, right: 8)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
//size to all model screens to one.....NOW!!!!!!!!
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = UIScreen.main.bounds.width
return CGSize(width: (width - 28)/2, height: 350) // width & height are the same to make a square cell
}
}
// MARK: - CollectionView Layout Delegate
extension WallpaperCVC : UICollectionViewDelegateFlowLayout {
// Header peralax
fileprivate func setupCollectionViewLayout() {
let padding: CGFloat = 16
if let layout = wpMain.collectionViewLayout as? UICollectionViewFlowLayout {
layout.sectionInset = .init(top: padding, left: 0, bottom: padding, right: 0)
}
}
// Header peralax
fileprivate func setupCollectionView(){
wpMain.contentInsetAdjustmentBehavior = .never
wpMain.register(headerView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerId)
}
// Header peralax
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerId, for: indexPath)
return headerView
}
// Header peralax
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return .init(width: view.frame.width, height: 400)
}
}
the cell looks like this
import UIKit
import Spring
import UICountingLabel
class WallpaperCVCell: UICollectionViewCell {
override func layoutSubviews() {
super.layoutSubviews()
// self.textLabel?.frame = self.bounds
}
func configure() {
layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale
contentView.layer.cornerRadius = 7
contentView.layer.masksToBounds = true
// contentView.layer.borderWidth = 1.0
// contentView.layer.borderColor = UIColor.black.cgColor
}
#IBOutlet weak var collectionCount: UICountingLabel!
#IBOutlet weak var collWallpaperImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
}
}
&& here is the video...the initial scroll lags then it goes away but it does it everytime
iPhone app lag video
EDIT: Ok in the end i remode Kingfisher & installed SDWebImage this took care of the problem.
Based on the given code there is nothing that should cause the lag. If you could share a short video of the lag that might help understand the problem better.
And also if you could share the code that is in your cell.configure() method that would also help.
I do have a few suggestions for you that might help, you can try them on.
first of all you can move your device check
if DeviceType.IS_IPHONE_5 {
result = CGSize(width: 160, height: 280)
}
if DeviceType.IS_IPHONE_6 {
result = CGSize(width: 187, height: 314)
}
if DeviceType.IS_IPHONE_6P {
result = CGSize(width: 207, height: 320)
}
if DeviceType.IS_IPHONE_4_OR_LESS {
result = CGSize(width: 151, height: 200)
}
in viewDidLoad which will help your collection view layout to size your cells faster and not having to check this every time the user scrolls
Something like this
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
return result;
}
and
override func viewDidLoad() {
super.viewDidLoad()
self.title = self.categoryName // Name to the controller or section selected
setupDeviceSize()
// go go gadget data...
getData()
buttonGray()
setupCollectionViewLayout()
setupCollectionView()
}
func setupDeviceSize(){
if DeviceType.IS_IPHONE_5 {
result = CGSize(width: 160, height: 280)
}
if DeviceType.IS_IPHONE_6 {
result = CGSize(width: 187, height: 314)
}
if DeviceType.IS_IPHONE_6P {
result = CGSize(width: 207, height: 320)
}
if DeviceType.IS_IPHONE_4_OR_LESS {
result = CGSize(width: 151, height: 200)
}
}
Second is you can create a global variable for your place holder image
let loadImage = UIImage(named: "skullLoading")
and use that in your cellForItemAtIndexpath, which will again save loading of the placeholder image every time your cellForItemAtIndexpath is called.
You should always keep your cellForItemAtIndexpath as light as possible for a smooth scrolling experience.
Hope this helps.
I am currently working on a particular problem where I have to add a UITableView and a couple of UICollectionViews along with a couple of labels in a single screen.
Here is the mockup:-
Right now, This is how my view looks (I am just working on the UI for now):-
The UICollectionViews below 'Live now' and 'Related Stories' are horizontally scrollable and in the middle of those UICollectionViews is a UITableView
Rather than having to compress these subviews inside the UIViewController class that I have built, I wish to remake it in such a way that the whole view is scrollable while keeping the same scrolling experience of the subviews currently set in the view.
I considered using another UITableview that encompasses all the views(the collection views and the table view), but then, the scrolling of that particular table view would cause a bad scrolling experience with the table view that I have to add.
The above could be said for using a UIScrollView (UICollectionViews that would be added would have no problem since they are being scrolled horizontally).
Would it be best to use a UICollectionView?
Any Suggestion that could help is welcome. Do let me know if there is anything from my side
Here is my source code:
import UIKit
import SnapKit
import EasyPeasy
class ArticleViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UITableViewDelegate, UITableViewDataSource {
var liveLabel = UILabel()
var engageLabel = UILabel()
var storyLabel = UILabel()
var livelayout = UICollectionViewFlowLayout.init()
var storylayout = UICollectionViewFlowLayout.init()
var liveCollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())
var storyCollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout.init())
var liveRows = [
"https://images.sportsflashes.com/australia-win-first-t20i-after-nail-biting-finish-at-vizag1551028137.jpg",
"https://images.sportsflashes.com/australia-win-first-t20i-after-nail-biting-finish-at-vizag1551028137.jpg",
"https://images.sportsflashes.com/australia-win-first-t20i-after-nail-biting-finish-at-vizag1551028137.jpg",
"https://images.sportsflashes.com/australia-win-first-t20i-after-nail-biting-finish-at-vizag1551028137.jpg",
"https://images.sportsflashes.com/australia-win-first-t20i-after-nail-biting-finish-at-vizag1551028137.jpg",
"https://images.sportsflashes.com/australia-win-first-t20i-after-nail-biting-finish-at-vizag1551028137.jpg",
"https://images.sportsflashes.com/australia-win-first-t20i-after-nail-biting-finish-at-vizag1551028137.jpg"
]
var articleRows = [
"https://c.ndtvimg.com/2019-03/6dkbrsrg_varun-dhawan_625x300_14_March_19.jpg",
"https://c.ndtvimg.com/2019-03/6dkbrsrg_varun-dhawan_625x300_14_March_19.jpg",
"https://c.ndtvimg.com/2019-03/6dkbrsrg_varun-dhawan_625x300_14_March_19.jpg",
"https://c.ndtvimg.com/2019-03/6dkbrsrg_varun-dhawan_625x300_14_March_19.jpg",
"https://c.ndtvimg.com/2019-03/6dkbrsrg_varun-dhawan_625x300_14_March_19.jpg",
"https://c.ndtvimg.com/2019-03/6dkbrsrg_varun-dhawan_625x300_14_March_19.jpg",
"https://c.ndtvimg.com/2019-03/6dkbrsrg_varun-dhawan_625x300_14_March_19.jpg",
"https://c.ndtvimg.com/2019-03/6dkbrsrg_varun-dhawan_625x300_14_March_19.jpg",
"https://c.ndtvimg.com/2019-03/6dkbrsrg_varun-dhawan_625x300_14_March_19.jpg"
]
var storyRows = [
"https://www.anime-planet.com/images/characters/takuma-mamizuka-83947.jpg",
"https://www.anime-planet.com/images/characters/takuma-mamizuka-83947.jpg",
"https://www.anime-planet.com/images/characters/takuma-mamizuka-83947.jpg",
"https://www.anime-planet.com/images/characters/takuma-mamizuka-83947.jpg",
"https://www.anime-planet.com/images/characters/takuma-mamizuka-83947.jpg",
"https://www.anime-planet.com/images/characters/takuma-mamizuka-83947.jpg",
"https://www.anime-planet.com/images/characters/takuma-mamizuka-83947.jpg",
"https://www.anime-planet.com/images/characters/takuma-mamizuka-83947.jpg",
"https://www.anime-planet.com/images/characters/takuma-mamizuka-83947.jpg"
]
let table = UITableView()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.view.backgroundColor = UIColor.white
if self.navigationController == nil {
return
}
self.navigationController?.isNavigationBarHidden = false
self.navigationItem.leftBarButtonItem = UIBarButtonItem(image:(UIImage(named: "back_arrow")?.withRenderingMode(.alwaysOriginal)), style:.plain, target:self, action:#selector(backPress))
var dateBarButtonItem = UIBarButtonItem(title: "Mar 2019", style: .plain, target: self, action: nil)
dateBarButtonItem.tintColor = UIColor.black
self.navigationItem.rightBarButtonItem = dateBarButtonItem
// Create a navView to add to the navigation bar
let navView = UIView()
// Create the label
let nameLabel = UILabel()
nameLabel.text = "Pavan Vasan"
nameLabel.sizeToFit()
nameLabel.center = navView.center
nameLabel.textAlignment = NSTextAlignment.center
// Create the image view
let image = UIImageView()
image.image = UIImage(named: "twitter")
// To maintain the image's aspect ratio:
let imageAspect = image.image!.size.width/image.image!.size.height
// Setting the image frame so that it's immediately before the text:
image.frame = CGRect(x: nameLabel.frame.origin.x-nameLabel.frame.size.height*imageAspect, y: nameLabel.frame.origin.y, width: nameLabel.frame.size.height*imageAspect, height: nameLabel.frame.size.height)
image.contentMode = UIView.ContentMode.scaleAspectFit
// Add both the label and image view to the navView
navView.addSubview(nameLabel)
navView.addSubview(image)
// Set the navigation bar's navigation item's titleView to the navView
self.navigationItem.titleView = navView
// Set the navView's frame to fit within the titleView
navView.sizeToFit()
}
override func viewDidLoad() {
super.viewDidLoad()
setupLiveCollectionView()
setupTable()
setupStoryCollectionView()
}
func setupLiveCollectionView() {
self.view.addSubview(self.liveLabel)
self.liveLabel.text = "Live now"
self.liveLabel.font = UIFont.boldSystemFont(ofSize: 17.5)
self.liveLabel.textColor = UIColor.black
self.liveLabel.textAlignment = .center
self.liveLabel.easy.layout(
Left(10).to(self.view),
Top(75).to(self.view)
)
livelayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
livelayout.itemSize = CGSize(width: 75, height: 75)
livelayout.scrollDirection = .horizontal
liveCollectionView = UICollectionView(frame: CGRect(x: 0, y: 100, width: self.view.frame.width, height: 95), collectionViewLayout: livelayout)
liveCollectionView.dataSource = self
liveCollectionView.delegate = self
liveCollectionView.register(LiveViewCell.self, forCellWithReuseIdentifier: "MyCell")
liveCollectionView.backgroundColor = UIColor.white
liveCollectionView.showsHorizontalScrollIndicator = false
self.view.addSubview(liveCollectionView)
}
func setupStoryCollectionView() {
self.view.addSubview(self.storyLabel)
self.storyLabel.text = "Related Stories"
self.storyLabel.font = UIFont.boldSystemFont(ofSize: 17.5)
self.storyLabel.textColor = UIColor.black
self.storyLabel.textAlignment = .center
self.storyLabel.easy.layout(
Left(10).to(self.view),
Top(15).to(self.table)
)
storylayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
storylayout.itemSize = CGSize(width: 120, height: 120)
storylayout.scrollDirection = .horizontal
storyCollectionView = UICollectionView(frame: CGRect(x: 0, y: 250 + self.view.bounds.height*0.40, width: self.view.frame.width, height: 130), collectionViewLayout: storylayout)
storyCollectionView.dataSource = self
storyCollectionView.delegate = self
storyCollectionView.register(StoryViewCell.self, forCellWithReuseIdentifier: "StoryCell")
storyCollectionView.backgroundColor = UIColor.white
storyCollectionView.showsHorizontalScrollIndicator = false
self.view.addSubview(storyCollectionView)
}
func setupTable() {
table.delegate = self
table.dataSource = self
table.register(ArticleTableViewCell.self, forCellReuseIdentifier: "ArticleCell")
table.separatorStyle = .none
self.view.addSubview(table)
self.table.easy.layout(
Top(15).to(self.liveCollectionView),
Left(0).to(self.view),
Width(self.view.bounds.width),
Height(self.view.bounds.height*0.4)
)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.liveCollectionView {
return self.liveRows.count
} else if collectionView == self.storyCollectionView {
return self.storyRows.count
}
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.liveCollectionView {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath as IndexPath) as! LiveViewCell
myCell.configure(self.liveRows[indexPath.row])
return myCell
} else if collectionView == self.storyCollectionView {
let myCell = collectionView.dequeueReusableCell(withReuseIdentifier: "StoryCell", for: indexPath as IndexPath) as! StoryViewCell
myCell.configure(self.storyRows[indexPath.row])
myCell.layer.borderColor = UIColor.black.cgColor
myCell.layer.cornerRadius = 15
myCell.layer.borderWidth = 0.5
myCell.layer.masksToBounds = true
return myCell
}
return UICollectionViewCell()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.articleRows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ArticleCell", for: indexPath) as! ArticleTableViewCell
cell.configure(self.articleRows[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 325
}
#objc func backPress(sender:UIButton!) {
self.navigationController?.popViewController(animated: true)
}
}
It would be preferable in complex screens to use only one main list view type(UITableView or UICollectionView).
In it u can add UIStackView or more UITableViews or UICollectionView.
In your case i would have used a UICollectionView and change the size of the cell according to your needs
You can use a single tableview for full screen and create custom cell by adding UICollectionView inside that.
Having nested scroll views could be against the HIG guidelines, which advocates not to place a scroll view inside of another scroll view.
In fact, similar to our use case, Apple has given the example of their Stocks app, where stock quotes scroll vertically above company-specific information that scrolls horizontally.
For more details, please refer to the HIG documentation at https://developer.apple.com/design/human-interface-guidelines/ios/views/scroll-views/
You can do it without any major changes in your code just below updates.
First, need to set constraint outlet of Tableview Height. e.g consTblHeight
Add Tableview size change observer.
func setupTable() {
table.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.new, context: nil)
}
Observer Method
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
self.consTblHeight.constant = self.table.contentSize.height
self.view.layoutIfNeeded()
}
So I have some code that I am using to auto scroll a collectionView and it is giving me lots of problems. It works based off of a timer that calls a certain function every 3.5 seconds to scroll to a certain item in the collectionView. The code is included belwo
import UIKit
class HomeFeedCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout, UIScrollViewDelegate {
var homeFeedController: HomeFeedController?
let emptyView = UIView()
//Timer user for call autoscroller of top collection view
private var timer:Timer?
var scrollTimer = Timer()
private let cellId = "cellId"
var featuredEvents: [Event]?{
didSet {
homeFeedCollectionView.reloadData()
}
}
var titles: String? {
didSet {
guard let titles = titles else {
return
}
// let attributedText = NSMutableAttributedString(string: titles, attributes: [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 100)])
sectionNameLabel.text = titles
// sectionNameLabel.attributedText = attributedText
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setTimer()
setupViews()
}
func setTimer(){
//auto scroll method to call every 2.5 seconds interval
self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(self.startTimer(theTimer:)), userInfo: nil, repeats: true)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let sectionNameLabel : UILabel = {
let sectionNameLabel = UILabel()
sectionNameLabel.font = UIFont(name:"HelveticaNeue-CondensedBlack", size: 36.0)
return sectionNameLabel
}()
lazy var emptyLabel: UILabel = {
let emptyLabel = UILabel()
emptyLabel.text = "Sorry We Currently Have No Events, \n In This Category Near You"
emptyLabel.font = UIFont(name: "Avenir", size: 14)
emptyLabel.numberOfLines = 0
emptyLabel.textAlignment = .center
return emptyLabel
}()
lazy var iconImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
return imageView
}()
let homeFeedCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .clear
return cv
}()
#objc func setupViews(){
backgroundColor = .clear
addSubview(homeFeedCollectionView)
addSubview(sectionNameLabel)
sectionNameLabel.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 2, paddingLeft: 4, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
homeFeedCollectionView.anchor(top: sectionNameLabel.bottomAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 4, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
homeFeedCollectionView.delegate = self
homeFeedCollectionView.dataSource = self
homeFeedCollectionView.showsHorizontalScrollIndicator = false
homeFeedCollectionView.register(HomeFeedEventCell.self, forCellWithReuseIdentifier: cellId)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let currentEventCount = featuredEvents?.count else{
return 0
}
if currentEventCount == 0 {
print("no events")
setupEmptyDataSet()
}else{
emptyView.removeFromSuperview()
}
return currentEventCount
}
#objc func setupEmptyDataSet(){
self.addSubview(emptyView)
emptyView.backgroundColor = .clear
emptyView.snp.makeConstraints { (make) in
make.edges.equalTo(self)
}
emptyView.addSubview(iconImageView)
iconImageView.image = UIImage(named: "icons8-face-100")
iconImageView.snp.makeConstraints { (make) in
make.center.equalTo(emptyView)
}
emptyView.addSubview(emptyLabel)
emptyLabel.snp.makeConstraints { (make) in
make.bottom.equalTo(iconImageView.snp.bottom).offset(30)
make.left.right.equalTo(emptyView)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 20, left: 5, bottom: 20, right: 5)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: frame.width - 40, height: frame.height - 40)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let eventDetails = EventDetailViewController()
eventDetails.currentEvent = featuredEvents?[indexPath.item]
homeFeedController?.navigationController?.pushViewController(eventDetails, animated: true)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! HomeFeedEventCell
cell.event = featuredEvents?[indexPath.item]
var rowIndex = indexPath.row
let numberOfRecords = (featuredEvents?.count)! - 1
if rowIndex < numberOfRecords {
rowIndex = rowIndex + 1
}else{
rowIndex = 0
}
scrollTimer = Timer.scheduledTimer(timeInterval: 3.5, target: self, selector: #selector(self.startTimer(theTimer:)), userInfo: rowIndex, repeats: true)
return cell
}
#objc func startTimer(theTimer: Timer){
UIView.animate(withDuration: 1.0, delay: 0, options: .curveEaseOut, animations: {
if let currentIndexPath = self.homeFeedCollectionView.indexPathsForVisibleItems.last{
//Check visible cell is last cell of top collection view then set first index as visible
if currentIndexPath.item == self.homeFeedCollectionView.numberOfItems(inSection: 0)-1{
let nextIndexPath = NSIndexPath(item: 0, section: 0)
//top collection view scroller in first item
self.homeFeedCollectionView.scrollToItem(at: nextIndexPath as IndexPath, at: .right, animated: false)
}else{
//create next index path from current index path of the top collection view
let nextIndexPath = NSIndexPath(item: currentIndexPath.item + 1, section: 0)
//top collection view scroller to next item
self.homeFeedCollectionView.scrollToItem(at: nextIndexPath as IndexPath, at: .left, animated: true)
}
}
}) { (finished) in
if finished {
print("done")
}
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.homeFeedCollectionView.scrollToNearestVisibleCollectionViewCell()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
self.homeFeedCollectionView.scrollToNearestVisibleCollectionViewCell()
}
}
}
Im not really sure what I am doing wrong, to be honest. But my problems with this approach are
When you I first enter the collectionView it goes straight to the third item
It sometimes scrolls to a certain item may be the first or the third and then it just gets stuck there and stops moving. It tried to move but it just keeps getting stuck.
You have a viewController for the view that shows up what you want and contains collectionView. This class should implement UICollectionViewDataSource and UICollectionViewDelegate:
class viewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource
in this class you should implement the numberOfItemsInSection and cellForItemAt methods.
and you need another class for cells in the collectionView:
class customCell: UICollectionViewCell
in this class you could define IBOutlets or what ever you want related to each cell NOT collectionView
and in last you should fire the timer in viewDidLoad (or viewDidAppear) instead of cellForItemAt delegate,
that func calls for each row then you start timers as number of cells that your collectionView has.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
scrollTimer = Timer.scheduledTimer(timeInterval: 3.5, target: self, selector: #selector(self.startTimer(theTimer:)), userInfo: rowIndex, repeats: true)
}
You can use this pod https://cocoapods.org/pods/AutoScrollCollectionView
https://github.com/mohankrishnameruva/AutoScrollCollectionView
Subclass your collection view from 'AutoScrollCollectionView' in storyboard and
whenever you want to start scroll call this method
on collectionView startAutoScrolling(withTimeInterval: TimeInterval(exactly: 2.0)!)
I have a button inside a collection view, but unlike my other buttons around the app, this one doesn't highlight when you tap it. I have tried a few things that were suggested such as turning on Shows Touch on Highlight on the button, and setting delaysContentTouches to false.
func createCollectionView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 15, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: view.frame.width, height: 50)
cv = UICollectionView(frame: view.frame, collectionViewLayout: layout)
cv.frame = CGRectMake(0, 50, view.frame.width, 250);
cv.dataSource = self
cv.delegate = self
cv.registerNib(UINib(nibName: "cvCell", bundle: nil), forCellWithReuseIdentifier: "cell")
cv.backgroundColor = UIColor.whiteColor()
//suggested on other SO answers:
cv.delaysContentTouches = false
for view in cv.subviews {
if view is UIScrollView {
(view as? UIScrollView)!.delaysContentTouches = false
break
}
}
}
//Button:
var onTap: ((cvCell) -> Void)?
#IBAction func btn(sender: AnyObject) {
onTap?(self)
}
//CollectionView select:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! cvCell
cell.btn.setTitle("follow", forState: .Normal)
}
Any ideas?
If your button type is cell.btn.buttonType == UIButtonType.system than it's Highlighted effect on button title display automatically. otherwise cell.btn.buttonType == UIButtonType.custom than you need to give your own Highlighted effect.