Related
I ran into a problem when using the simplest UICollectionView and UICollectionViewFlowLayout.
The collection itself works fine, but when the cell is removed, there are problems with the animation.
Here is a code example that demonstrates the problem:
class Cell: UICollectionViewCell { }
class MyViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var items = Array(repeating: [UIColor.red, .blue, .yellow, .cyan, .magenta, .green], count: 100).flatMap { $0 }
lazy var collectionView: UICollectionView = {
let collectionViewLayout = UICollectionViewFlowLayout()
collectionViewLayout.scrollDirection = .vertical
collectionViewLayout.minimumLineSpacing = 10
collectionViewLayout.minimumInteritemSpacing = 10
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(Cell.self, forCellWithReuseIdentifier: "Cell")
collectionView.delegate = self
collectionView.dataSource = self
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
NSLayoutConstraint.activate([
.init(item: collectionView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0),
.init(item: collectionView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0),
.init(item: collectionView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0),
.init(item: collectionView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0),
])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
cell.backgroundColor = items[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
CGSize(width: UIScreen.main.bounds.width, height: 300)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
items.remove(at: indexPath.item)
collectionView.deleteItems(at: [indexPath])
}
}
The problem itself looks like this (in slow motion):
You can notice how the next cell disappears for the duration of the animation, and at the end of it it appears.
Question: what am I doing wrong? This is a very basic use of a collection and I'm confused by these issues.
From the rest of the answers, the reason for this glitch became clear - there are problems with the deletion animation if some of the new cells are off the screen.
I solved the problem by adding layout invalidation to the animation block:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.performBatchUpdates({
self.items.remove(at: indexPath.item)
collectionView.deleteItems(at: [indexPath])
collectionView.collectionViewLayout.invalidateLayout()
}, completion: nil)
}
It seems like there is a problem with your array.
It's created dynamically and has conflicts with UICollectionView's implementation.
Try replacing your array with the following static array.
var items = [UIColor.red, .blue, .yellow, .cyan, .magenta, .green, .red, .blue, .yellow, .cyan, .magenta, .green]
Here is what it looks like after changing it.
You have to subclass UICollectionViewFlowLayout and override these two methods
initialLayoutAttributesForAppearingItem
finalLayoutAttributesForDisappearingItem
by changing "attributes" properties you will get different effects just change the line 59 animation duration and check them out.
import UIKit
class Cell: UICollectionViewCell { }
class ViewController: UIViewController ,UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var items = Array(repeating: [UIColor.red, .blue, .yellow, .cyan, .magenta, .green], count: 100).flatMap { $0 }
lazy var collectionView: UICollectionView = {
let collectionViewLayout = MyCollectionLayout()
collectionViewLayout.scrollDirection = .vertical
collectionViewLayout.minimumLineSpacing = 10
collectionViewLayout.minimumInteritemSpacing = 10
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.register(Cell.self, forCellWithReuseIdentifier: "Cell")
collectionView.delegate = self
collectionView.dataSource = self
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
NSLayoutConstraint.activate([
.init(item: collectionView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0),
.init(item: collectionView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0),
.init(item: collectionView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0),
.init(item: collectionView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0),
])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
cell.backgroundColor = items[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
CGSize(width: UIScreen.main.bounds.width, height: 300)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
UIView.animate(withDuration: 0.2) {
self.collectionView.performBatchUpdates({
self.items.remove(at: indexPath.item)
collectionView.deleteItems(at: [indexPath])
}, completion: nil)
}
}
}
class MyCollectionLayout : UICollectionViewFlowLayout {
var rowOffset : CGFloat = 0.0
var deleteIndexPaths : Array<IndexPath> = []
var insertIndexPaths : Array<IndexPath> = []
override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
super.prepare(forCollectionViewUpdates: updateItems)
self.deleteIndexPaths = Array<IndexPath>()
self.insertIndexPaths = Array<IndexPath>()
for updateItem in updateItems {
if (updateItem.updateAction == .delete){
self.deleteIndexPaths.append(updateItem.indexPathBeforeUpdate!)
}else if (updateItem.updateAction == .insert){
self.insertIndexPaths.append(updateItem.indexPathAfterUpdate!)
}
}
}
override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
var attributes = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath)
if(attributes == nil){
attributes = self.layoutAttributesForItem(at: itemIndexPath)
}
attributes?.alpha = 1
return attributes
}
override func finalizeCollectionViewUpdates()
{
super.finalizeCollectionViewUpdates()
self.deleteIndexPaths = [];
self.insertIndexPaths = [];
}
override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
var attributes = super.finalLayoutAttributesForDisappearingItem(at: itemIndexPath)
if ((attributes == nil)){
attributes = self.layoutAttributesForItem(at: itemIndexPath)
}
attributes?.transform = CGAffineTransform(scaleX: 0, y: 0)
attributes?.zIndex = -1
attributes?.alpha = 1
return attributes
}
}
I have a simple UICollectionViewController that is one section with a header and footer pinned to visible bounds. When I click a textField in the footer, the footer automatically animates above the keyboard and the collectionView scrolls to show the cells that otherwise would have been hidden by the keyboard as you would expect. However, when I click one outside of the keyboard, and dismiss it with a call to self.view.endEditing(true) the footer and collectionView do not react unless the collectionView is scrolled near the bottom. If the collectionView is scrolled near the bottom, the footer and collectionView animate as expected.
How can I force the footer and collectionView to animate properly every time?
In the images below, the header is orange, footer is green, and there are 10 cells that alternate red and blue.
Actual Behavior (occurs when you are not scrolled near the bottom of the collectionView when the keyboard dismisses)
Desired Behavior (occurs when you are scrolled at or near the bottom of the collectionView when the keyboard dismisses)
I know there are some methods to achieve this with Notifications when the keyboard appears and disappears but I would prefer to just use the default UICollectionViewController behavior if possible. Is there some configuration or setting I am missing?
Code:
New Single View App. Delete StoryBoard and ViewController.swift. Delete the main storyboard entry from the info plist file.
AppDelegate.swift
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = CollectionViewController.init()
return true
}
}
CollectionViewController.swift
import UIKit
private let reuseIdentifier = "Cell"
private let collectionViewHeaderFooterReuseIdentifier = "MyHeaderFooterClass"
class CollectionViewController: UICollectionViewController {
init() {
let collectionViewFlowLayout = UICollectionViewFlowLayout.init()
collectionViewFlowLayout.sectionHeadersPinToVisibleBounds = true
collectionViewFlowLayout.sectionFootersPinToVisibleBounds = true
super.init(collectionViewLayout: collectionViewFlowLayout)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
// Register cell classes
self.collectionView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
self.collectionView?.register(MyHeaderFooterClass.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier)
self.collectionView?.register(MyHeaderFooterClass.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier)
self.collectionView?.backgroundColor = UIColor.white
self.collectionView?.dataSource = self
self.collectionView?.delegate = self
}
}
// MARK: UICollectionViewDelegate
extension CollectionViewController {
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
DispatchQueue.main.async {
self.view.endEditing(true)
self.collectionViewLayout.invalidateLayout()
self.collectionView?.layoutIfNeeded()
}
}
}
// MARK: - UICollectionViewDataSource
extension CollectionViewController {
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return 10
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
switch indexPath.row % 2 {
case 0:
cell.backgroundColor = UIColor.red
case 1:
cell.backgroundColor = UIColor.blue
default:
cell.backgroundColor = UIColor.gray
}
return cell
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier, for: indexPath)
if kind == UICollectionView.elementKindSectionHeader {
view.backgroundColor = UIColor.orange
}
else { //footer
view.backgroundColor = UIColor.green
}
return view
}
}
// MARK: - Collection View Flow Layout Delegate
extension CollectionViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 100)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 100)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 100)
}
}
MyHeaderFooterClass
import UIKit
class MyHeaderFooterClass: UICollectionReusableView {
let textField: UITextField = {
let view = UITextField.init()
view.placeholder = "enter text here"
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(textField)
self.setNeedsUpdateConstraints()
}
override func updateConstraints() {
textFieldConstraints()
super.updateConstraints()
}
private func textFieldConstraints() {
NSLayoutConstraint(item: textField, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: textField, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: textField, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: 0.0).isActive = true
NSLayoutConstraint(item: textField, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
From documentation:
func layoutIfNeeded()
Use this method to force the view to update its layout immediately.
func invalidateLayout()
This method invalidates the layout of the collection view itself and
returns right away.
Solution:
Looking at your code, you are already updating collectionViewLayout. So, you don't need to force update collectionView again. Therefore, simply removing self.collectionView?.layoutIfNeeded() alone will solve your issue.
Change your code as below:
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
DispatchQueue.main.async {
self.view.endEditing(true)
self.collectionViewLayout.invalidateLayout()
//self.collectionView?.layoutIfNeeded()
}
}
Good afternoon.
I am creating a simple little app and I have a simple "feature" in mind that I cannot figure out how to implement, being rather new to programming. I have a UICollectionView with cells within a cell of another UICollectionView and when I tap one of those cells they trigger the following didselect and diddeselect methods:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) {
cell.layer.borderColor = UIColor.flatBlack.cgColor
cell.layer.borderWidth = 3
cell.layer.cornerRadius = 7
cell.backgroundColor = GradientColor(.topToBottom, frame: cell.frame, colors: [UIColor.flatRed.withAlphaComponent(0.2), UIColor.white])
} else {return}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) {
cell.layer.borderColor = UIColor.clear.cgColor
cell.layer.borderWidth = 0
cell.layer.cornerRadius = 0
cell.backgroundColor = .white
} else {return}
}
Cell content: Its kinda long but i'll post the code if asked (programmatically created cells). But basically I have a few UIImageViews, two UITextViews, one UITextLabel and one UIButton inside each cell. (So the cells are declared in a separate class not in the class which is the UICollectionViewDelegate and DataSource).
Question: I want to add so that when I tap anywhere within the cells individual frame I want to have it selected even if I tap on a UITextView (at moment if I tap the textview it goes into edit mode of that view). I want to be able to edit the textview if I tap a second time on it.
- How can I implement this?
Cell creation Code:
class EventsCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white
setupViews()
}
//MARK: - Declaration of cell organelles.
//separatorView for a separating line between cells
let textView: GrowingTextView = { //GrowingTextView is a pod it is a regular UITextView but with some perks.
let tv = GrowingTextView()
tv.backgroundColor = .clear
tv.allowsEditingTextAttributes = true
tv.isScrollEnabled = false
tv.font = UIFont(name: "Didot", size: 16)
tv.textContainerInset = UIEdgeInsetsMake(4, 4, 4, 6)
tv.placeholder = "Write your event text here"
tv.placeholderColor = UIColor.lightGray
tv.autoresizingMask = .flexibleHeight
tv.isUserInteractionEnabled = false
return tv
}()
let eventPlaceholderMarkImage: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "placeHolderEventTitleMark")
return iv
}()
func setupViews() {
addSubview(textView)
addSubview(eventPlaceholderMarkImage)
let eventsPinImageH = frame.width / 2 - 7
let cellHeight = frame.height
let cellWidth = frame.width
//MARK: - Constraints for EventsCell
addConstraintsWithFormat(format: "H:|-16-[v0(\(frame.width - 32))]-16-|", views: textView)
addConstraintsWithFormat(format: "V:|-47-[v0]-16-|", views: textView)
addConstraint(NSLayoutConstraint(item: eventPlaceholderMarkImage, attribute: .bottom, relatedBy: .equal, toItem: textView, attribute: .top, multiplier: 1, constant: -5))
addConstraint(NSLayoutConstraint(item: eventPlaceholderMarkImage, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 0, constant: 20))
//Followed by much more constraints
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then in the class I create the UICollectionView:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: EventCellID, for: indexPath)
return cell
}
Thank you for reading my post!
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) {
cell.yourTextView.isUserInteractionEnbled = true //1
cell.layer.borderColor = UIColor.flatBlack.cgColor
cell.layer.borderWidth = 3
cell.layer.cornerRadius = 7
cell.backgroundColor = GradientColor(.topToBottom, frame: cell.frame, colors: [UIColor.flatRed.withAlphaComponent(0.2), UIColor.white])
} else {return}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) {
cell.layer.borderColor = UIColor.clear.cgColor
cell.layer.borderWidth = 0
cell.layer.cornerRadius = 0
cell.backgroundColor = .white
} else {return}
}
override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillDisappear), name: Notification.Name.UIKeyboardWillHide, object: nil)
}
#objc func keyboardWillDisappear() {
cell.yourTextView.isUserInteractionEnbled = false //2
}
When creating the cell :
...
cell.yourTextView.isUserInteractionEnabled = false // 3
1 : the first Time your cell is being selected, your textView won't be selectable due to what we did at step (3), after the first tap we're enabling the textView for the second touch
2 : Whenever we dismiss the keyboard after editing the textview we are setting interactions to false so that we would be able to select the cell on tap and not the textView
I've developed a crossword app for android and managed to create the grid using the GridView class provided in the android support library and buttons (This one).
I've tried to use UICollectionView with a custom layout but it seems to be designed for other purposes. Does anyone know what can I use to create the same (or a similar) thing in iOS?
Is there a library that makes easier to crate a fixed table like this one?
Note: the number of columns and rows is fixed 10x10
I'd prefer to use Swift over Obj-C and i'm not caring about the keyboard and the menu yet.
Thank you very much
I attach a screenshot of the android app.
Sorry for my english, it is still bad :)
UIViewController should work fine for your needs. Try something like this:
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var collectionView: UICollectionView!
var gridSize: CGFloat = 10
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 1
layout.minimumLineSpacing = 1
collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: 0, height: 0), collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
view.addConstraints([
NSLayoutConstraint(item: collectionView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: collectionView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: collectionView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 20),
NSLayoutConstraint(item: collectionView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: view.frame.width) ])
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return Int(pow(Double(gridSize), 2))
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
// Set up your cells here
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let size = view.frame.width / gridSize - 1
return CGSize(width: size, height: size)
}
}
Setting the backgroundColor of your cells will give you this:
Currently in my code, I'm programmatically adding a UITextView within a UITableViewCell and added the ability to auto-resize the cell based on how much content is typed by the user.
It currently looks like so:
override func viewDidLoad()
{
tableView.estimatedRowHeight = 30.0
tableView.rowHeight = UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
// Dequeue the cell to load data
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if indexPath.section == 0
{
// Code
}
if indexPath.section == 1
{
let textView: UITextView = UITextView()
textView.delegate = self
textView.textColor = UIColor.black
textView.isScrollEnabled = false
textView.translatesAutoresizingMaskIntoConstraints = false
cell.addSubview(textView)
let leadingConstraint = NSLayoutConstraint(item: cell.contentView, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: textView, attribute: NSLayoutAttribute.leading, multiplier: 1.0, constant: 8.0)
let trailingConstraint = NSLayoutConstraint(item: cell.contentView, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: textView, attribute: NSLayoutAttribute.trailing, multiplier: 1.0, constant: -8.0)
cell.contentView.addConstraint(leadingConstraint)
cell.contentView.addConstraint(trailingConstraint)
let topConstraint = NSLayoutConstraint(item: cell.contentView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: textView, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: cell.contentView, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: textView, attribute: NSLayoutAttribute.bottom, multiplier: 1.0, constant: 0)
cell.contentView.addConstraint(topConstraint)
cell.contentView.addConstraint(bottomConstraint)
}
else if indexPath.section == 2
{
// Code
}
return cell
}
override func numberOfSections(in tableView: UITableView) -> Int
{
return 3
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 1
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
let headerCell = tableView.dequeueReusableCell(withIdentifier: "HeaderCell")
let titleLabel = headerCell?.viewWithTag(1) as! UILabel
if section == 0
{
titleLabel.text = "Title"
}
else if section == 1
{
titleLabel.text = "Title"
}
else if section == 2
{
titleLabel.text = "Title"
}
return headerCell?.contentView
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView?
{
let footerCell = tableView.dequeueReusableCell(withIdentifier: "FooterCell")
return footerCell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
if indexPath.section == 1
{
return UITableViewAutomaticDimension
}
return super.tableView(tableView, heightForRowAt: indexPath)
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
return 35.0
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat
{
return 15.0
}
func textViewDidChange(_ textView: UITextView)
{
tableView.beginUpdates()
tableView.endUpdates()
}
I only care about section 1 because that's the only cell that I want to auto-resize the cell's height. All other sections should maintain a static cell height.
The problem I have is that as soon as I press the enter key for the FIRST time in the UITextView to skip to the next line, I see very quickly some "ghost" cells and get a warning stating:
no index path for table cell being reused
The cell's height DOES dynamically re-size itself accordingly and after the initial return key is pressed, any subsequent new lines does not re-produce the "ghost" cells, but I still get the warnings.
What am I seem to be doing wrong?
Thanks
TextView is inherited from scrollView and hence the content size of textView can be greater than frame (In which case textView will scroll). In order for tableView to calculate the automatic size for cell it needs components with implicit size like label or button. So in order for tableView to calculate implicit size for textView you should disable scroll.
textView.isScrollEnabled = false
Should allow your cell to expand :)
EDIT:
Looks like this happens when a reusable cell is used as section header (as in your case) Have a look at this : What is the meaning of the "no index path for table cell being reused" message in iOS 6/7?
Suggestion :
Create a normal xib add a view and return it as a section header
else follow the answer posted in above link
Try this.
Add this method too.
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat{
if(indexPath.row == 0){
return heightForFirstRow
}
return heightForStaticCell
}