dynamic collectionView in TableView - ios

I have a dynamic collectionView in TableView and there are three errors:
the cells are not displayed correctly when the application starts
when I do inserItem the cells flicker as if they are rebooting
and most importantly when collectionView.insertItems the first four cells are hidden, the video
thanks
MainViewController
class ViewController: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
setting()
}
func setting(){
getData()
tableView.dataSource = self
tableView.delegate = self
tableView.estimatedRowHeight = UITableView.automaticDimension
tableView.rowHeight = UITableView.automaticDimension
}
//load data
func pagination(_ completion: (()->())?){
SmartNetworkSevrice.getGoods(with: url) { [unowned self] (data) in
guard data.modals.count > 0 else {
self.tableView.tableFooterView = nil
return
}
self.goods.append(contentsOf: data.modals)
self.offSet += data.modals.count
DispatchQueue.main.async {
let indexPath = IndexPath(row: 0, section: 0)
self.tableView.tableFooterView = nil
if self.goods.count == data.modals.count || self.isRefresh {
self.tableView.reloadRows(at: [indexPath], with: .none)
} else {
if let cell = self.tableView.cellForRow(at: indexPath) as? TVCellGoods {
UIView.performWithoutAnimation {
self.tableView.beginUpdates()
cell.insertGoods(data.modals)
cell.layoutIfNeeded()
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height
self.tableView.endUpdates()
}
}
}
completion?()
}
}
// define bottom of tableView
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView == self.tableView else { return }
if (!isMoreDataLoading) {
// Вычислить позицию длины экрана до нижней части результатов
let scrollViewContentHeight = scrollView.contentSize.height
let scrollOffsetThreshold = scrollViewContentHeight - scrollView.bounds.size.height
if(scrollView.contentOffset.y > scrollOffsetThreshold && scrollView.isDragging) {
isMoreDataLoading = true
self.tableView.isScrollEnabled = false;
self.tableView.isScrollEnabled = true;
pagination(nil)
}
}
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "goods", for: indexPath) as? TVCellGoods else { return UITableViewCell() }
guard bestGoods.count != 0, goods.count != 0 else { return UITableViewCell() }
cell.delegate = self
cell.configure(bestGoods, goods, categories)
// автообновление высоты
self.tableView.beginUpdates()
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height
self.tableView.endUpdates()
return cell
}
}
extension ViewController: UITableViewDelegate, CallDelegate {
func callMethod() {}
func callMethod(push vc:UIViewController) {
self.navigationController?.pushViewController(vc, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
TableViewCell with collectionView
class TVCellGoods: UITableViewCell {
#IBOutlet weak var collectionView:UICollectionView!
#IBOutlet weak var collectionViewHeight:NSLayoutConstraint!
weak var delegate:CallDelegate?
var bestGoods = [Goods]() // лучшие товары
var goods = [Goods]() // все товары
var categories = [Menu]()
override func awakeFromNib() {
super.awakeFromNib()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.tag = 2
collectionView.isScrollEnabled = false
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func configure(_ best:[Goods],_ goods:[Goods], _ category:[Menu]) {
self.bestGoods = best
self.goods = goods
self.categories = category
self.collectionView.reloadData()
}
func insertGoods(_ data:[Goods]) {
self.goods.append(contentsOf: data)
let count = self.bestGoods.count + self.categories.count + self.goods.count
let indexPaths = ((count - data.count) ..< count)
.map { IndexPath(row: $0, section: 0) }
self.collectionView.performBatchUpdates({
self.collectionView.insertItems(at: indexPaths)
}, completion: nil)
}
}
and CollectionViewCell
class CVCellGoods: UICollectionViewCell {
#IBOutlet weak var bgView: UIView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var title: UILabel!
#IBOutlet weak var price: UILabel!
#IBOutlet weak var delivery: UIImageView!
#IBOutlet weak var premium: UIImageView!
override func prepareForReuse() {
super.prepareForReuse()
delivery.image = nil
premium.image = nil
title.text = nil
price.text = nil
imageView.image = nil
imageView.sd_cancelCurrentImageLoad()
}
override func awakeFromNib() {
super.awakeFromNib()
imageView.backgroundColor = UIColor(red: 215/255, green: 215/255, blue: 215/255, alpha: 1)
self.contentView.layer.cornerRadius = 5
self.contentView.layer.masksToBounds = true
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 0.5)
self.layer.shadowRadius = 1
self.layer.shadowOpacity = 0.3
self.layer.masksToBounds = false
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
}
}
EDIT: this gives a small bug at startup
TVCellGoods.swift
override func layoutSubviews() {
super.layoutSubviews()
self.collectionView.collectionViewLayout.invalidateLayout()
}

I just figured out how to solve your problem.
You gonna have to invalidate your collection view layout, as you are using an collectionView inside a tableView just add the following method on TVCellGoods.swift
override func layoutSubviews() {
super.layoutSubviews()
self.collectionView.collectionViewLayout.invalidateLayout()
}
I tested it, and worked!!
see video

It's not a good idea to add a scroll view inside another scroll view!
Do you have an example project in order to help you?

the cells are not displayed correctly when the application starts
You calculate collectionView itemSize in ViewDidLoad,
but in ViewDidLoad TableView & TableviewCell width not equal ViewController.
override func viewDidLoad() {
super.viewDidLoad()
print(view.frame.size.width) //320
print(tableView.frame.size.width) //414
}
You can try reloadData in ViewDidAppear.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print(view.frame.size.width) //320
print(tableView.frame.size.width) //320
tableView.reloadData()
}
when collectionView.insertItems the first four cells are hidden
Subclass UICollectionViewFlowLayout
class MyCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
TVCellGoods.swift
override func awakeFromNib() {
super.awakeFromNib()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.tag = 2
collectionView.isScrollEnabled = false
collectionView.collectionViewLayout = MyCollectionViewFlowLayout()
}

Related

collectionView.collectionViewLayout.invalidateLayout()

I have a dynamic collectionView in TableView and there are an bug when I add invalidateLayout (picture is below)
but if I delete invalidateLayout the first four cells are hidden and the layout is not displayed correctly, the video
please help I searched the whole stack but nothing helped thanks
if you need test project
MainViewController
class ViewController: UIViewController, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
setting()
}
func setting(){
getData()
tableView.dataSource = self
tableView.delegate = self
tableView.estimatedRowHeight = UITableView.automaticDimension
tableView.rowHeight = UITableView.automaticDimension
}
//load data
func pagination(_ completion: (()->())?){
SmartNetworkSevrice.getGoods(with: url) { [unowned self] (data) in
guard data.modals.count > 0 else {
self.tableView.tableFooterView = nil
return
}
self.goods.append(contentsOf: data.modals)
self.offSet += data.modals.count
DispatchQueue.main.async {
let indexPath = IndexPath(row: 0, section: 0)
self.tableView.tableFooterView = nil
if self.goods.count == data.modals.count || self.isRefresh {
self.tableView.reloadRows(at: [indexPath], with: .none)
} else {
if let cell = self.tableView.cellForRow(at: indexPath) as? TVCellGoods {
UIView.performWithoutAnimation {
self.tableView.beginUpdates()
cell.insertGoods(data.modals)
cell.layoutIfNeeded()
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height
self.tableView.endUpdates()
}
}
}
completion?()
}
}
// define bottom of tableView
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView == self.tableView else { return }
if (!isMoreDataLoading) {
// Вычислить позицию длины экрана до нижней части результатов
let scrollViewContentHeight = scrollView.contentSize.height
let scrollOffsetThreshold = scrollViewContentHeight - scrollView.bounds.size.height
if(scrollView.contentOffset.y > scrollOffsetThreshold && scrollView.isDragging) {
isMoreDataLoading = true
self.tableView.isScrollEnabled = false;
self.tableView.isScrollEnabled = true;
pagination(nil)
}
}
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "goods", for: indexPath) as? TVCellGoods else { return UITableViewCell() }
guard bestGoods.count != 0, goods.count != 0 else { return UITableViewCell() }
cell.delegate = self
cell.configure(bestGoods, goods, categories)
// автообновление высоты
self.tableView.beginUpdates()
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height
self.tableView.endUpdates()
return cell
}
}
extension ViewController: UITableViewDelegate, CallDelegate {
func callMethod() {}
func callMethod(push vc:UIViewController) {
self.navigationController?.pushViewController(vc, animated: true)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
TableViewCell with collectionView
class TVCellGoods: UITableViewCell {
#IBOutlet weak var collectionView:UICollectionView!
#IBOutlet weak var collectionViewHeight:NSLayoutConstraint!
weak var delegate:CallDelegate?
var bestGoods = [Goods]() // лучшие товары
var goods = [Goods]() // все товары
var categories = [Menu]()
override func layoutSubviews() {
super.layoutSubviews()
self.collectionView.collectionViewLayout.invalidateLayout()
}
override func awakeFromNib() {
super.awakeFromNib()
collectionView.delegate = self
collectionView.dataSource = self
collectionView.tag = 2
collectionView.isScrollEnabled = false
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func configure(_ best:[Goods],_ goods:[Goods], _ category:[Menu]) {
self.bestGoods = best
self.goods = goods
self.categories = category
self.collectionView.reloadData()
}
func insertGoods(_ data:[Goods]) {
self.goods.append(contentsOf: data)
let count = self.bestGoods.count + self.categories.count + self.goods.count
let indexPaths = ((count - data.count) ..< count)
.map { IndexPath(row: $0, section: 0) }
self.collectionView.performBatchUpdates({
self.collectionView.insertItems(at: indexPaths)
}, completion: nil)
}
}
and CollectionViewCell
class CVCellGoods: UICollectionViewCell {
#IBOutlet weak var bgView: UIView!
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var title: UILabel!
#IBOutlet weak var price: UILabel!
#IBOutlet weak var delivery: UIImageView!
#IBOutlet weak var premium: UIImageView!
override func prepareForReuse() {
super.prepareForReuse()
delivery.image = nil
premium.image = nil
title.text = nil
price.text = nil
imageView.image = nil
imageView.sd_cancelCurrentImageLoad()
}
override func awakeFromNib() {
super.awakeFromNib()
imageView.backgroundColor = UIColor(red: 215/255, green: 215/255, blue: 215/255, alpha: 1)
self.contentView.layer.cornerRadius = 5
self.contentView.layer.masksToBounds = true
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 0.5)
self.layer.shadowRadius = 1
self.layer.shadowOpacity = 0.3
self.layer.masksToBounds = false
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
}
}
Edit
if I use
self.collectionView.performBatchUpdates({
self.collectionView.insertItems(at: indexPaths)
}, completion: { _ in
self.collectionView.reloadItems(at: indexPaths)
})
with invalidateLayout then I get an empty space at the bottom of the screen
but if I delete invalidateLayout I get wrong Layout
if you don’t have it like mine, then discard the project with your improvements
SECOND Edit
I noticed that there is an indentation on the iPhone 11 Pro and everything is fine on the iPhone 11 Pro Max
UPDATE: I verified this works on the simulators for iPhone SE, 11, and 11 Max with no dead space at the end of the collection view.
My simulator is set to dark mode to show that the bottom of the collection view is just enough for the loading icon.
I had to make a few changes for this to properly size. First, I changed your insertGoods function in TVCellGoods class to reload only the newly added items after inserting and it no longer has missing items at the top of the list:
self.collectionView.performBatchUpdates({
self.collectionView.insertItems(at: indexPaths)
}, completion: { _ in
self.collectionView.reloadItems(at: indexPaths)
})
A big problem was setting the estimated row height for your tableView cells. I've removed the estimatedHeightForRowAt override and replaced your heightForRowAt with this:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
return (tableView.frame.width / 2.5) + 20
} else if indexPath.row == 1 {
return 70
} else {
guard let cell = tableView.cellForRow(at: indexPath) as? TVCellGoods else { return UITableView.automaticDimension }
// print(cell.collectionViewHeight.constant)
return cell.collectionViewHeight.constant
}
}
I also had to add layoutIfNeeded to your cellForRow at in your ViewController UITableViewDataSource extension:
self.tableView.beginUpdates()
cell.collectionViewHeight.constant = cell.collectionView.collectionViewLayout.collectionViewContentSize.height
self.tableView.endUpdates()
cell.collectionView.layoutIfNeeded()
self.tableView.layoutIfNeeded()
Lastly, I had to update your layoutSubviews to assign the correct height after invalidating the layout in TVCellGoods:
override func layoutSubviews() {
super.layoutSubviews()
self.collectionView.collectionViewLayout.invalidateLayout()
self.collectionViewHeight.constant = self.collectionView.collectionViewLayout.collectionViewContentSize.height
}
Here is the updated source code: https://drive.google.com/open?id=1ldr4Ml2prZKmr1C4i1s5Sg7LvvxL7-jO

tableview last section is scrambled when setting row height to 0 swift

I added an arrow to fold the section when clicked, when I fold the last section (e.g. setting the row height to 0 in cellForRow, all the rows in the last section get mixed up, as can be seen in the image below:
Can anyone suggest any reason why this should happen if I'm setting the height of the row to 0?
Here's the relevant code:
viewForHeaderInSection:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
let profileCompletion = clientFields?.profileCompletion ?? 0
headerView.profileComplete.text = "Profile".localized() + " \(profileCompletion)% " + "Complete".localized()
headerView.profileProgress.progress = Float(profileCompletion) * 0.01
headerView.profileProgress.progressTintColor = Constants.Client.getProfileCompletenessColor(profileCompletion)
headerView.delegate = self
headerView.section = section
headerView.isOpen = self.sectionsStatuses[section].shouldDisplaySection
return headerView
}
else {
let height = (section == 0) ? FIRST_HEADER_HEIGHT : HEADER_HEIGHT
let nib = UINib(nibName: "ClientDetailsSectionHeaderCell", bundle: nil)
guard let returnedView = nib.instantiate(withOwner: self, options: nil)[0] as? ClientDetailsSectionHeaderCell else {
return UIView()
}
returnedView.delegate = self
returnedView.section = section
returnedView.isOpen = self.sectionsStatuses[section].shouldDisplaySection
returnedView.frame = CGRect(x: 0, y: 0, width: view.frame.size.width, height: height)
returnedView.heightConstraint.constant = height
returnedView.mainView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
returnedView.mainView.backgroundColor = Constants.ParentForm.headerBgColor
// Draw separators
if DRAW_SECTION_SEPARATORS || DRAW_SECTION_TOP_SEPARATOR {
returnedView.createTopSeparator(color: Constants.ParentForm.separatorColor)
}
if DRAW_SECTION_SEPARATORS || DRAW_SECTION_BOTTOM_SEPARATOR {
returnedView.createBottomSeparator(color: Constants.ParentForm.separatorColor)
}
if isSectionTitleHidden == false {
let xOffset = HEADER_X_OFFSET
let yOffset = section == 0 ? FIRST_HEADER_Y_OFFSET : HEADER_Y_OFFSET
returnedView.title.frame = CGRect(x: xOffset, y: yOffset, width: view.frame.size.width - xOffset, height: height - yOffset)
returnedView.title.text = self.sectionsArray[section].uppercased().localized()
returnedView.title.font = Constants.ParentForm.headerFont
returnedView.title.textColor = Constants.ParentForm.headerTextColor
returnedView.title.sizeToFit()
}
return returnedView
}
}
heightForRow
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if sectionsStatuses[indexPath.section].shouldDisplaySection {
return super.tableView(tableView, heightForRowAt: indexPath)
}
return CGFloat(0)
}
cellForRow
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if self.sectionsArray[indexPath.section] != "ADDRESSES" {
if let clientFields = self.clientFields {
self.dataMap = Utils.convertObjectToMap(item: clientFields)
let birthdayDate = clientFields.birthdayDateString
self.dataMap["birthdayDate"] = birthdayDate
let createdDate = clientFields.createdDateString
self.dataMap["createdDate"] = createdDate
}
}
else {
if let clientAddresses = self.clientAddresses, clientAddresses.count > 0 {
self.dataMap = Utils.convertObjectToMap(item: clientAddresses[Int(floor(Double(indexPath.row) / 8.0))])
}
}
return super.tableView(tableView, cellForRowAt: indexPath)
}
protocol function
func openCloseSection(openSection: Bool, section: Int) {
self.sectionsStatuses[section].shouldDisplaySection = openSection
self.tableView.reloadData()
}
ClientDetailsSectionHeaderCell
import UIKit
protocol SectionViewerProtocol {
func openCloseSection(openSection: Bool, section: Int)
}
class ClientDetailsSectionHeaderCell: UITableViewCell {
#IBOutlet weak var mainView: UIView!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
#IBOutlet weak var accessoryButton: UIButton!
#IBOutlet weak var title: UILabel!
var section = 0
var delegate: SectionViewerProtocol?
var isOpen: Bool? {
didSet {
if let isOpen = self.isOpen {
accessoryButton.setImage(UIImage(named: isOpen ? "fill588": "fill589"), for: .normal)
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
#IBAction func openCloseSection(_ sender: UIButton) {
if let isOpen = self.isOpen {
self.isOpen = !isOpen
delegate?.openCloseSection(openSection: !isOpen, section: section)
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
Try setting clipsToBounds of your cell or any enclosing UIView to true.

UICollection View inside Table Cell does not show images in Swift 3

I put UICollectionView inside table cell in Home page. Each category row has their contents image. But, my problem is that when I run the app, the images under second row/second category do not show whenever I run the app.
The images will show after I click more button and it will goes to details UI. Then, I click on Back button and reach the Home page. At this time, the images shows properly. I tried many codes but I don't know where it is wrong.
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tableView.reloadData()
self.tableView.reloadData()
self.tableView.layoutIfNeeded()
}
override func viewDidAppear(_ animated: Bool) {
tableView.reloadData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//getSearchWords()
DispatchQueue.main.async {
self.tableView.reloadData()
}
self.tableView.reloadData()
self.tableView.layoutIfNeeded()
}
override func viewDidLoad() {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// debugPrint("HOme Table View Cell")
let cell = tableView.dequeueReusableCell(withIdentifier: "homecell") as! HomeCategoryRowCell
let list = sections[(indexPath as NSIndexPath).row]
cell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
cell.collectionViewOffset = self.storedOffsets[indexPath.row] ?? 0
DispatchQueue.main.async {
cell.categoryTitle.text = list.package_name
cell.mainAssociatedURL.text = list.package_url
}
cell.categoryTitle.font = UIFont.boldSystemFont(ofSize: 17.0)
cell.collectionView.tag = indexPath.row
cell.parentVC = self
cell.collectionView.reloadData()
return cell
}
extension Home : UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return sections[collectionView.tag].packageTable.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "videoCell", for: indexPath) as! HomeVideoCell
let list = sections[collectionView.tag].packageTable[indexPath.row]
if(list.poster_image_url != StringResource().posterURL){
let url = NSURL(string: list.poster_image_url)
do{
if let url = url {
_ = try Data(contentsOf:url as URL)
poster_url = list.poster_image_url
}
}catch let error {
//debugPrint("ERRor ::\(error)")
poster_url = StringResource().posterURL
}
}else{
poster_url = StringResource().posterURL
}
AsyncImageLoader.sharedLoader.imageForUrl(urlString: poster_url) { (image, url) -> () in
DispatchQueue.main.async(){
cell.movieTitle.text = list.name
if(url == StringResource().posterURL){
cell.imageView.image = UIImage(named: "sample_cover")
}else{
cell.imageView.image = image
}
}
}
// cell.layer.shouldRasterize = true
// cell.layer.rasterizationScale = UIScreen.main.scale
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Home Collection view at row \(collectionView.tag) selected index path \(indexPath)")
let list = sections[collectionView.tag].packageTable[indexPath.row]
self.prefs.set(list.poster_image_url, forKey: "poster_image_url")
self.prefs.set(list.name, forKey: "name")
self.prefs.set(list.assets_id, forKey: "VEDIO_ID")
debugPrint(list.name)
debugPrint(list.assets_id)
}
}
These above codes are written in Home.swift.
In HomeCell.swift,
class HomeCell : UITableViewCell {
var parentVC: UIViewController?
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var categoryTitle: UILabel!
#IBOutlet weak var SeeAll: UIButton!
#IBOutlet weak var mainAssociatedURL: UILabel!
let prefs:UserDefaults = UserDefaults.standard
var catId: String! = ""
var associated_url: String!
override func awakeFromNib() {
super.awakeFromNib()
SeeAll.setTitle(NSLocalizedString("See All >", comment: ""), for: .normal)
SeeAll.titleLabel!.font = UIFont (name: "Tharlon", size: 14)
}
#IBAction func btnSeeAll(_ sender: Any) {
catId = categoryTitle.text!
associated_url = mainAssociatedURL.text!
self.prefs.setValue(1, forKey: "PROVIDER_ID")
self.prefs.set(catId, forKey: "PROVIDER_NAME")
self.prefs.set(associated_url, forKey: "associated_url")
}
override func layoutSubviews() {
super.layoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}
}
extension HomeCategoryRowCell {
func setCollectionViewDataSourceDelegate<D: UICollectionViewDataSource & UICollectionViewDelegate>(_ dataSourceDelegate: D, forRow row: Int) {
self.collectionView.delegate = dataSourceDelegate
self.collectionView.dataSource = dataSourceDelegate
self.collectionView.tag = row
self.collectionView.setContentOffset(self.collectionView.contentOffset, animated:false) // Stops collection view if it was scrolling.
self.collectionView.reloadData()
}
var collectionViewOffset: CGFloat {
get { return collectionView.contentOffset.x }
set { collectionView.contentOffset.x = newValue }
}
}
Can anyone help me please?
Edit controller like below, you are calling too many reloadData.
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.tableView.layoutIfNeeded()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//getSearchWords()
DispatchQueue.main.async {
self.tableView.reloadData()
}
self.tableView.layoutIfNeeded()
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
and add:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView .deselectRow(at: indexPath, animated: false)
}

tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) Doesn't work when I add a UITapGestureRecognizer to my custom UITableViewCell

I have a very primitive problem at hand and that is when I have a tap gesture recognizer added to my custom cell I cant call the didSelectRowAt method.
My Code is the following:
import UIKit
class LanguageViewController: UIViewController {
#IBOutlet weak var titleLabel: AvenirUILabel!
#IBOutlet weak var backButton: UIButton!
#IBOutlet weak var submitButton: PinkUIButton!
#IBOutlet weak var addLanguageButton: UIButton!
#IBOutlet weak var addAnotherLanguageLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
var numberOfLanguagesSpoken = 2
var pickersStartHeight: CGFloat!
let downArrowImage = UIImage(named: "arrow_drop_down")
let BGColor = UIColor.white.withAlphaComponent(0.1)
var tag = 1001
var selectedCellHeight: CGFloat = 88.0
var unselectedCellHeight: CGFloat = 44.0
var selectedCellIndexPath: IndexPath?
let languages = ["Akan", "Amharic", "Arabic", "Assamese", "Awadhi", "Azerbaijani", "Balochi", "Belarusian", "Bengali", "Bhojpuri", "Burmese", "Cebuano", "Chewa", "Chhattisgarhi", "Chittagonian", "Czech", "Deccan", "Dhundhari", "Dutch", "Eastern Min", "English", "French", "Fula", "Gan Chinese", "German", "Greek", "Gujarati", "Haitian Creole", "Hakka", "Haryanvi", "Hausa", "Hiligaynon/Ilonggo", "Hindi ", "Hmong", "Hungarian", "Igbo", "Ilocano", "Italian", "Japanese", "Javanese", "Jin", "Kannada", "Kazakh", "Khmer", "Kinyarwanda", "Kirundi", "Konkani", "Korean", "Kurdish", "Madurese", "Magahi", "Maithili", "Malagasy", "Malay", "Malayalam", "Mandarin", "Marathi", "Marwari", "Mossi", "Nepali", "Northern Min", "Odia", "Oromo", "Pashto", "Persian", "Polish", "Portuguese", "Punjabi", "Quechua", "Romanian", "Russian", "Saraiki", "Serbo-Croatian", "Shona", "Sindhi", "Sinhalese", "Somali", "Southern Min", "Spanish", "Sundanese", "Swedish", "Sylheti", "Tagalog/Filipino", "Tamil", "Telugu", "Thai", "Turkish", "Turkmen", "Ukrainian", "Urdu", "Uyghur", "Uzbek", "Vietnamese", "Wu", "Xhosa", "Xiang ", "Yoruba", "Yue ", "Zhuang", "Zulu"]
var proficiency = ["Basic","Conversational","Proficient","Fluent","Native"]
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = backgroundTint
// initial set up
let height = self.view.bounds.height
titleLabel.textColor = .white
backButton.addTarget(self, action: #selector(goBack), for: .allTouchEvents)
tableView.dataSource = self
tableView.delegate = self
tableView.register(LanguageProficiencyTableViewCell.self, forCellReuseIdentifier: "Language")
tableView.backgroundColor = backgroundTint
tableView.allowsSelection = true
tableViewHeightConstraint.constant = 2*height/9
addLanguageButton.tintColor = .white
addLanguageButton.addTarget(self, action: #selector(addNewLanguage), for: .allTouchEvents)
addAnotherLanguageLabel.textColor = .white
submitButton.addTarget(self, action: #selector(submitChanges), for: .allEvents)
}
override func viewWillLayoutSubviews() {
unselectedCellHeight = self.view.bounds.height/9
selectedCellHeight = CGFloat(unselectedCellHeight * 2.0)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func goBack() {
self.dismiss(animated: true, completion: nil)
}
func submitChanges() {
self.dismiss(animated: true) {
//
}
}
func addNewLanguage() {
numberOfLanguagesSpoken += 1
UIView.animate(withDuration: 0.5) {
//
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
extension LanguageViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfLanguagesSpoken
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Language", for: indexPath) as! LanguageProficiencyTableViewCell
cell.parentVC = self
cell.backgroundColor = .clear
cell.languages = self.languages
cell.proficiency = self.proficiency
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if selectedCellIndexPath == indexPath {
return selectedCellHeight
} else {
return unselectedCellHeight
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
//
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedCellIndexPath != nil && selectedCellIndexPath == indexPath as IndexPath {
selectedCellIndexPath = nil
} else {
selectedCellIndexPath = indexPath as IndexPath
}
tableView.beginUpdates()
tableView.endUpdates()
if selectedCellIndexPath != nil {
// This ensures, that the cell is fully visible once expanded
tableView.scrollToRow(at: indexPath as IndexPath, at: .none, animated: true)
}
}
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
return indexPath
}
}
import UIKit
class LanguageProficiencyTableViewCell: UITableViewCell {
let languageLabel = IndentedTextUILabel()
var languagePicker = UIPickerView()
weak var parentVC: LanguageViewController!
var cellHeight: CGFloat = 80
var open = false
var languages = [""]
var proficiency = [""]
var pickedLanguage = ""
var pickedProficiency = ""
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(languageLabel)
self.contentView.addSubview(languagePicker)
self.contentView.backgroundColor = .clear
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override func layoutSubviews() {
let width = self.bounds.width
let height = cellHeight
let BGColor = UIColor.white.withAlphaComponent(0.1)
languageLabel.frame = CGRect(x: 0.0, y: 1.0, width: width, height: height - 1.0)
languageLabel.textColor = .white
languageLabel.backgroundColor = BGColor
languageLabel.text = "Language(Proficiency)"
languageLabel.leftInset = 25.0
let downArrow = UIButton()
let downArrowImage = UIImage(named: "arrow_drop_down")
downArrow.setImage(downArrowImage, for: .normal)
downArrow.center.y = height/2
downArrow.center.x = 4 * width/5
languageLabel.addSubview(downArrow)
let tap = UITapGestureRecognizer(target: self, action: #selector(cellTapped))
self.addGestureRecognizer(tap)
languagePicker.frame = CGRect(x: 0.0, y: self.cellHeight + 1.0, width: self.bounds.width, height: 0.0)
languagePicker.delegate = self
languagePicker.dataSource = self
languagePicker.backgroundColor = UIColor.white.withAlphaComponent(0.05)
languagePicker.selectRow(20, inComponent: 0, animated: true)
let backGroundView = UIView(frame: CGRect(x: 0.0, y: 1.0, width: width, height: height - 1.0))
backGroundView.backgroundColor = .clear
self.selectedBackgroundView = backGroundView
}
func cellTapped() {
UIView.animate(withDuration: 0.2, animations: {
if !self.open {
self.languagePicker.frame.size.height = self.cellHeight - 2.0
} else {
self.languagePicker.isHidden = true
}
}, completion: { (_) in
if self.open {
self.languagePicker.frame.size.height = 0.0
self.languagePicker.isHidden = false
}
self.open = !self.open
})
}
}
It will not work because the tap gesture is taking the tap on itself and not sending the tap to the views underneath it.
You can make the tap gesture send the tap to be passed to the background views by
mySwipeGesture.cancelsTouchesInView = false
as described in How to pass a 'tap' to UIButton that is underneath UIView with UISwipeGestureRecognizer?
Also if you are using tap gesture just to dismiss your keyboard you can do so in didSelectAt method of the tableView and do any other thing after dismissing the keyboard in that method.
I'm doing something like what you have done and it's working for me.
You have to add the gesture recognizer not inside the TableViewCell class but from your Controller class when you set the cell.
Like this:
class LanguageViewController: UIViewController {
#IBOutlet weak var titleLabel: AvenirUILabel!
#IBOutlet weak var backButton: UIButton!
#IBOutlet weak var submitButton: PinkUIButton!
#IBOutlet weak var addLanguageButton: UIButton!
#IBOutlet weak var addAnotherLanguageLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
var numberOfLanguagesSpoken = 2
var pickersStartHeight: CGFloat!
let downArrowImage = UIImage(named: "arrow_drop_down")
let BGColor = UIColor.white.withAlphaComponent(0.1)
var tag = 1001
var selectedCellHeight: CGFloat = 88.0
var unselectedCellHeight: CGFloat = 44.0
var selectedCellIndexPath: IndexPath?
let languages = ["Akan", "Amharic", "Arabic", "Assamese", "Awadhi", "Azerbaijani", "Balochi", "Belarusian", "Bengali", "Bhojpuri", "Burmese", "Cebuano", "Chewa", "Chhattisgarhi", "Chittagonian", "Czech", "Deccan", "Dhundhari", "Dutch", "Eastern Min", "English", "French", "Fula", "Gan Chinese", "German", "Greek", "Gujarati", "Haitian Creole", "Hakka", "Haryanvi", "Hausa", "Hiligaynon/Ilonggo", "Hindi ", "Hmong", "Hungarian", "Igbo", "Ilocano", "Italian", "Japanese", "Javanese", "Jin", "Kannada", "Kazakh", "Khmer", "Kinyarwanda", "Kirundi", "Konkani", "Korean", "Kurdish", "Madurese", "Magahi", "Maithili", "Malagasy", "Malay", "Malayalam", "Mandarin", "Marathi", "Marwari", "Mossi", "Nepali", "Northern Min", "Odia", "Oromo", "Pashto", "Persian", "Polish", "Portuguese", "Punjabi", "Quechua", "Romanian", "Russian", "Saraiki", "Serbo-Croatian", "Shona", "Sindhi", "Sinhalese", "Somali", "Southern Min", "Spanish", "Sundanese", "Swedish", "Sylheti", "Tagalog/Filipino", "Tamil", "Telugu", "Thai", "Turkish", "Turkmen", "Ukrainian", "Urdu", "Uyghur", "Uzbek", "Vietnamese", "Wu", "Xhosa", "Xiang ", "Yoruba", "Yue ", "Zhuang", "Zulu"]
var proficiency = ["Basic","Conversational","Proficient","Fluent","Native"]
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = backgroundTint
// initial set up
let height = self.view.bounds.height
titleLabel.textColor = .white
backButton.addTarget(self, action: #selector(goBack), for: .allTouchEvents)
tableView.dataSource = self
tableView.delegate = self
tableView.register(LanguageProficiencyTableViewCell.self, forCellReuseIdentifier: "Language")
tableView.backgroundColor = backgroundTint
tableView.allowsSelection = true
tableViewHeightConstraint.constant = 2*height/9
addLanguageButton.tintColor = .white
addLanguageButton.addTarget(self, action: #selector(addNewLanguage), for: .allTouchEvents)
addAnotherLanguageLabel.textColor = .white
submitButton.addTarget(self, action: #selector(submitChanges), for: .allEvents)
}
override func viewWillLayoutSubviews() {
unselectedCellHeight = self.view.bounds.height/9
selectedCellHeight = CGFloat(unselectedCellHeight * 2.0)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func goBack() {
self.dismiss(animated: true, completion: nil)
}
func submitChanges() {
self.dismiss(animated: true) {
//
}
}
func addNewLanguage() {
numberOfLanguagesSpoken += 1
UIView.animate(withDuration: 0.5) {
//
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
extension LanguageViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfLanguagesSpoken
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Language", for: indexPath) as! LanguageProficiencyTableViewCell
cell.parentVC = self
cell.backgroundColor = .clear
cell.languages = self.languages
cell.proficiency = self.proficiency
let tap = UITapGestureRecognizer(target: self, action: #selector("cellTapped:"))
cell.addGestureRecognizer(tap)
return cell
}
//Change here you can't call languagePicker with self but you have to get it from the object tapped that in your case is you cell
func cellTapped(recognizer: UITapGestureRecognizer) {
UIView.animate(withDuration: 0.2, animations: {
if !self.open {
self.languagePicker.frame.size.height = self.cellHeight - 2.0
} else {
self.languagePicker.isHidden = true
}
}, completion: { (_) in
if self.open {
self.languagePicker.frame.size.height = 0.0
self.languagePicker.isHidden = false
}
self.open = !self.open
})
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if selectedCellIndexPath == indexPath {
return selectedCellHeight
} else {
return unselectedCellHeight
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
//
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedCellIndexPath != nil && selectedCellIndexPath == indexPath as IndexPath {
selectedCellIndexPath = nil
} else {
selectedCellIndexPath = indexPath as IndexPath
}
tableView.beginUpdates()
tableView.endUpdates()
if selectedCellIndexPath != nil {
// This ensures, that the cell is fully visible once expanded
tableView.scrollToRow(at: indexPath as IndexPath, at: .none, animated: true)
}
}
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
return indexPath
}
}
import UIKit
class LanguageProficiencyTableViewCell: UITableViewCell {
let languageLabel = IndentedTextUILabel()
var languagePicker = UIPickerView()
weak var parentVC: LanguageViewController!
var cellHeight: CGFloat = 80
var open = false
var languages = [""]
var proficiency = [""]
var pickedLanguage = ""
var pickedProficiency = ""
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(languageLabel)
self.contentView.addSubview(languagePicker)
self.contentView.backgroundColor = .clear
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override func layoutSubviews() {
let width = self.bounds.width
let height = cellHeight
let BGColor = UIColor.white.withAlphaComponent(0.1)
languageLabel.frame = CGRect(x: 0.0, y: 1.0, width: width, height: height - 1.0)
languageLabel.textColor = .white
languageLabel.backgroundColor = BGColor
languageLabel.text = "Language(Proficiency)"
languageLabel.leftInset = 25.0
let downArrow = UIButton()
let downArrowImage = UIImage(named: "arrow_drop_down")
downArrow.setImage(downArrowImage, for: .normal)
downArrow.center.y = height/2
downArrow.center.x = 4 * width/5
languageLabel.addSubview(downArrow)
languagePicker.frame = CGRect(x: 0.0, y: self.cellHeight + 1.0, width: self.bounds.width, height: 0.0)
languagePicker.delegate = self
languagePicker.dataSource = self
languagePicker.backgroundColor = UIColor.white.withAlphaComponent(0.05)
languagePicker.selectRow(20, inComponent: 0, animated: true)
let backGroundView = UIView(frame: CGRect(x: 0.0, y: 1.0, width: width, height: height - 1.0))
backGroundView.backgroundColor = .clear
self.selectedBackgroundView = backGroundView
}
}

Swift custom UIView IBOutlet nil , awakeFromNib() never called

I am trying to custom a UIView but when I call it, the outlets of custom view always nil, and awakeFromNib() never called, too. Here my CustomView:
class DropdownMenuView: UIView {
//MARK: Outlets
#IBOutlet var view: DropdownMenuView!
#IBOutlet weak var fadeView: UIView!
#IBOutlet weak var tableView: UITableView!
var selection:Selection = .Default
var iconType:IconType = .Default
var menuTitles:[String] = []
var cellHeight:CGFloat = 44
var textColorNomal:UIColor = UIColor.blackColor()
var textColorHighlight:UIColor = UIColor.redColor()
var isShown: Bool!
//MARK: Lifecycle
override func awakeFromNib() {
print("awakeFromNib")
super.awakeFromNib()
initSubviews()
configView()
}
override func layoutSubviews() {
super.layoutSubviews()
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
func initSubviews() {
if let nibsView = NSBundle.mainBundle().loadNibNamed("DropdownMenuView", owner: self, options: nil) as? [UIView] {
let nibRoot = nibsView[0]
self.addSubview(nibRoot)
nibRoot.frame = self.bounds
}
}
required init(uSelection: Selection = Selection.Default, uIconType:IconType = IconType.Default, uMenuTitles:[String]) {
self.selection = uSelection
self.iconType = uIconType
self.menuTitles = uMenuTitles
super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
}
private func configView() {
switch (selection) {
case Selection.Multiple:
tableView.allowsMultipleSelection = true
default:
tableView.allowsMultipleSelection = false
}
tableView.registerNib(UINib(nibName: "DropdownMenuCell", bundle: nil), forCellReuseIdentifier: "DropdownMenuCell")
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
hideMenu()
}
internal func show() {
if self.isShown == false {
self.showMenu()
}
}
internal func hide() {
if self.isShown == true {
self.hideMenu()
}
}
func showMenu() {
self.isShown = true
if let app = UIApplication.sharedApplication().delegate as? AppDelegate, let window = app.window {
window.bringSubviewToFront(self)
}
// Animation
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.alpha = 1
})
}
func hideMenu() {
self.isShown = false
// Animation
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.alpha = 0
})
}
}
//MARK: UITableViewDelegate methods
extension DropdownMenuView: UITableViewDelegate {
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat(menuTitles.count) * cellHeight
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return cellHeight
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("DropdownMenuCell") as! DropdownMenuCell
cell.lblTitle.text = menuTitles[indexPath.row]
cell.iconType = iconType
return cell
}
}
//MARK: UITableViewDataSource methods
extension DropdownMenuView: UITableViewDataSource {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menuTitles.count
}
}
I tried to remove outlets and connect again, but I wasn't lucky. It isn't working anymore. You can check my full demo project here:
https://www.dropbox.com/s/5udbl2kn9vnwjar/ModuleDemo.zip?dl=0
You click on "Label" on StartVC to call custom UIView.
I hope what I ask is possible. Any helps always are appropriate!

Resources