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.
Related
I’ve got a ViewController with a UICollectionView using an edge offset to lower its content on the screen, and a search bar that is added as a subview, whose frame has a negative y value so that it appears in the collection view above the content.
But as the search bar’s frame’s y value goes beyond a certain value (in this example, -45.0, but it depends on the size of the search bar), accessibility breaks: voice control will completely ignore it, and VoiceOver will ignore the search bar on the way down (it jumps directly from the ‘done’ button to the first collection view cell), although it will focus on the search bar on the way up.
If the frame's y > -45.0, accessibility functions as expected.
This isn’t caused by the search bar per se, as any UIView will cause the same issue
Is this a bug in UIAccessibility? Or is there something going on here that can explain this behaviour?
class ViewController: UIViewController {
var myCollectionView: UICollectionView?
var searchBar = UISearchBar()
let searchBarHeight: CGFloat = 56
override func viewDidLoad() {
super.viewDidLoad()
let view = UIView()
view.backgroundColor = .white
setupCollectionView()
addNavbar()
addSearchBar()
}
func setupCollectionView() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 80, height: 80)
myCollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
myCollectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MyCell")
myCollectionView?.backgroundColor = .red
myCollectionView?.contentInset = UIEdgeInsets(top: 200,
left: 0,
bottom: 0,
right: 0)
myCollectionView?.delegate = self
myCollectionView?.dataSource = self
view.addSubview(myCollectionView ?? UICollectionView())
self.view = view
}
func addNavbar() {
let navBar = UINavigationBar(frame: CGRect(x: 0, y: 44, width: view.frame.size.width, height: 44))
view.addSubview(navBar)
let navItem = UINavigationItem()
let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil)
navItem.rightBarButtonItem = doneItem
navBar.setItems([navItem], animated: false)
}
func addSearchBar() {
searchBar.backgroundColor = .brown
searchBar.frame = CGRect(x: 0,
y: -searchBarHeight * 2,
width: view.frame.width,
height: searchBarHeight)
myCollectionView?.addSubview(searchBar)
}
}
extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 8
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath)
cell.backgroundColor = .cyan
cell.isAccessibilityElement = true
cell.accessibilityLabel = "cell \(indexPath.row)"
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
cell?.backgroundColor = .darkGray
}
}
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!
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()
}
I'm trying to create a collectionView and populate data into it using RxSwift. However even though it seem to return objects in datasource.configureCell it doesn't show any cells. I suspect that there is something wrong with my setup in viewDidLoad ?
Setup collectionView
// Create a waterfall layout
let layout = CHTCollectionViewWaterfallLayout()
//Add CollectionView
self.collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height), collectionViewLayout: layout)
self.view.addSubview(collectionView)
//Customize
self.collectionView!.alwaysBounceVertical = true
// Collection view attributes
self.collectionView.autoresizingMask = [UIViewAutoresizing.flexibleHeight, UIViewAutoresizing.flexibleWidth]
self.collectionView.alwaysBounceVertical = true
//Register cell
collectionView.register(PetsaleCell.self, forCellWithReuseIdentifier: reuseIdentifier)
//Constraints
self.collectionView.snp.makeConstraints({ make in
make.bottom.equalTo(0)
make.left.equalTo(0)
make.right.equalTo(0)
make.top.equalTo(0)
})
//Datasource
setUpDataSource()
setUpDataSource
func setUpDataSource() {
dataSource.configureCell = { (_, tv, ip, animal: Animal) in
let cell = tv.dequeueReusableCell(withReuseIdentifier: self.reuseIdentifier, for: ip) as! PetsaleCell
cell.petCellViewModel = PetCellViewModel(animal: animal)
return cell
}
let loadNextPageTrigger = self.collectionView.rx.contentOffset
.flatMap { _ in
self.collectionView
.isNearBottomEdge(edgeOffset: 20.0)
? Observable.just(())
: Observable.empty()
}
animalViewModel.rx_animals(loadNextPageTrigger)
.asDriver(onErrorJustReturn: .empty).map { [SectionModel(model: "Animal", items: $0.animals)] }
.drive(collectionView.rx.items(dataSource: dataSource))
.addDisposableTo(disposeBag)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
return CGSize(width: 200, height: 200)
}
You need to set your delegate just after the dataSources binding.
collectionView
.rx.delegate
.setForwardToDelegate(self, retainDelegate: false)
I have a collectionView inside of a ViewController. When I swipe, it swipes to another Collection View Cell which is the height and width of the view.frame
I want to have an animation that starts whenever I start swiping, to then finish whenever the collection view locks into the centre of a cell just like the gif below. I don't have a label or textview in my code but could you point me in the right direction on how I would do this? Here's my relevent code:
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
cv.dataSource = self
cv.delegate = self
cv.isPagingEnabled = true
cv.showsHorizontalScrollIndicator = false
return cv
}()
let cellId = "cellId"
let loginCellId = "loginCellId"
let pages: [Page] = {
let firstPage = Page(imageName: "introduction_1")
let secondPage = Page(imageName: "introduction_2")
let thirdPage = Page(imageName: "introduction_3")
let fourthPage = Page(imageName: "introduction_4")
return [firstPage, secondPage, thirdPage, fourthPage]
}()
lazy var pageControl: UIPageControl = {
let pc = UIPageControl()
pc.pageIndicatorTintColor = .lightGray
pc.currentPageIndicatorTintColor = .darkGray
pc.numberOfPages = self.pages.count + 1
return pc
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
view.addSubview(pageControl)
_ = pageControl.anchor(nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 40)
collectionView.anchorToTop(view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
registerCells()
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let pageNumber = Int(targetContentOffset.pointee.x / view.frame.width)
pageControl.currentPage = pageNumber
}
fileprivate func registerCells() {
collectionView.register(PageCell.self, forCellWithReuseIdentifier: cellId)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: loginCellId)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return pages.count + 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == pages.count {
let loginCell = collectionView.dequeueReusableCell(withReuseIdentifier: loginCellId, for: indexPath)
return loginCell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! PageCell
let page = pages[indexPath.item]
cell.page = page
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
}
class PageCell: UICollectionViewCell {
var page: Page? {
didSet {
guard let page = page else {
return
}
imageView.image = UIImage(named: page.imageName)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.backgroundColor = .yellow
iv.clipsToBounds = true
return iv
}()
func setupViews() {
addSubview(imageView)
imageView.anchorToTop(topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
From my standpoint, in your case it is better to use UIPageViewController instead of UICollectionView. It has a whole bunch of features for you to implement the UI you've shown above. There is a good tutorial on this topic - https://www.veasoftware.com/posts/uipageviewcontroller-in-swift-xcode-62-ios-82-tutorial?rq=page