Spritekit - Tableview scrolling cut off - ios

I’m currently developing a Menu screen in my sprite kit game to show all of the items and i’ve used a tableview to achieve this because it allows me to have a uilabel for the item description.
my uitableview is subclassed as follows:
UITableview Class
import Foundation
import SpriteKit
import UIKit
class GameRoomTableView: UITableView,UITableViewDelegate,UITableViewDataSource {
var items: [String] = []
var descrip: [String] = []
var title: [String] = []
var isFirstViewAlreadyAdded = false
var isSecondViewAlreadyAdded = false
override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
self.delegate = self
self.dataSource = self
coreDataItemRetrieveval()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
return items.count
}
func coreDataItemRetrieveval() {
items.removeAll(); descrip.removeAll(); title.removeAll()
items.append("Player1")
descrip.append("Grown on Astrums Home world of Zaharia the forbidden fruit when eaten results in a headstart for the player")
title.append("Athia Fruit")
items.append("Player2")
descrip.append("HHHHHH HHHHHH HHHHHHHHHH HHHHHHH HHHHHHHH HHHHHHHH HHHHHHHHH HHHHHHHHH ")
title.append("AtTTTTTTT")
items.append("Player2")
descrip.append("TESTING ")
title.append("AtTTTTTTT")
items.append("Player2")
descrip.append("TESTING ")
title.append("AtTTTTTTT")
items.append("Player2")
descrip.append("TESTING ")
title.append("AtTTTTTTT")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
tableView.allowsSelection = false
tableView.showsVerticalScrollIndicator = false
tableView.backgroundColor = .clear
tableView.backgroundView = nil
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let CellIdentifier: String = "Cell"
var cell: UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: CellIdentifier)
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: nil)
//IMPLEMENT CORE DATA RETRIEVEAL AND SO ON TO MAKE IT BETTER USE APPEND ARRAYS AND SO ON TO GET THIS DONE AND IMPLEMENT QUANTITY LABEL.
cell?.imageView?.image = UIImage(named:(self.items[indexPath.section] + ".png"))
cell?.imageView?.transform = CGAffineTransform(scaleX: 0.5, y: 0.5);
cell?.textLabel?.text = self.descrip[indexPath.section]
cell?.textLabel?.numberOfLines = 0
cell?.textLabel?.adjustsFontSizeToFitWidth = true
cell?.textLabel?.frame = CGRect(x: (cell?.frame.size.width)! / 2.6, y: (cell?.frame.size.height)! / 1.7, width: 150, height: 50)
let textlabel2 = UILabel(frame: CGRect(x: (cell?.frame.size.width)! / 2.6, y: (cell?.frame.size.height)! / 1.4, width: 150, height: 50))
textlabel2.text = self.title[indexPath.section]
textlabel2.numberOfLines = 0
textlabel2.adjustsFontSizeToFitWidth = true
cell?.contentView.addSubview(textlabel2)
}
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200.00
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int){
view.tintColor = UIColor.clear
let header = view as! UITableViewHeaderFooterView
header.textLabel?.textColor = UIColor.white
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return " "
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.row)!")
}
}
GameScene:
class GameScene: SKScene {
var gameTableView = GameRoomTableView()
private var label : SKLabelNode?
override func didMove(to view: SKView) {
gameTableView.frame = CGRect(x:14,y:100, width: frame.maxX / 1.08, height: frame.maxY) //(scene?.view?.frame.maxY)!)
gameTableView.contentSize = CGSize(width: gameTableView.frame.size.width, height: gameTableView.frame.size.height)
self.scene?.view?.addSubview(gameTableView)
gameTableView.reloadData()
}
}
the only problem I have is when I scroll to the bottom of the tableview It seems that half of the last cell is cut off from being scrolled to and I can’t see it fully the tableview has multiple sections because I wanted gaps between each cell and that was the only way I could achieve It. How do I change the scrolling of the tableview to be longer so I can see all of the cells fully? I have tried looking into other answers on here and I've had no luck fixing it.

Your issue is in the line:
override func didMove(to view: SKView) {
gameTableView.frame = CGRect(x:14,y:100, width: frame.maxX / 1.08, height: frame.maxY)
...
This happened because your gameTableView height is bigger than the scene height:
To solve you can try to decrease the height of your table for example:
gameTableView.frame = CGRect(x:14,y:100, width: frame.maxX / 1.08, height: frame.maxY/2)

Related

I'm Creating an demo on Expandeble Tableview, Expanding is working Fine... But facing issue while didselect row is tapped

Expanding and Collapsing is working fine tableview, Only facing issue while didselect row is tapped, I'm getting same index evry time after selecting a row. I'm getting the same out put, I want to pass the data to next view but output isn't working properly.
Here's My Details...
My OutPut
My Model
struct ItemList {
var name: String
var items: [String]
var collapsed: Bool
init(name: String, items: [String], collapsed: Bool = false) {
self.name = name
self.items = items
self.collapsed = collapsed
}
}
My ViewController Class
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var sections = [ItemList]()
var items: [ItemList] = [
ItemList(name: "Mac", items: ["MacBook", "MacBook Air"]),
ItemList(name: "iPad", items: ["iPad Pro", "iPad Air 2"]),
ItemList(name: "iPhone", items: ["iPhone 7", "iPhone 6"])
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
}
TableView Extension
extension ViewController:UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerHeading = UILabel(frame: CGRect(x: 5, y: 10, width: self.view.frame.width, height: 40))
let imageView = UIImageView(frame: CGRect(x: self.view.frame.width - 30, y: 20, width: 20, height: 20))
if items[section].collapsed{
imageView.image = UIImage(named: "collapse")
}else{
imageView.image = UIImage(named: "expand")
}
let headerView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 60))
let tapGuesture = UITapGestureRecognizer(target: self, action: #selector(headerViewTapped))
tapGuesture.numberOfTapsRequired = 1
headerView.addGestureRecognizer(tapGuesture)
headerView.backgroundColor = UIColor.red
headerView.tag = section
headerHeading.text = items[section].name
headerHeading.textColor = .white
headerView.addSubview(headerHeading)
headerView.addSubview(imageView)
return headerView
}
func numberOfSections(in tableView: UITableView) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let itms = items[section]
return !itms.collapsed ? 0 : itms.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
cell.textLabel?.text = items[indexPath.section].items[indexPath.row]
return cell
}
#objc func headerViewTapped(tapped:UITapGestureRecognizer){
print(tapped.view?.tag)
if items[tapped.view!.tag].collapsed == true{
items[tapped.view!.tag].collapsed = false
}else{
items[tapped.view!.tag].collapsed = true
}
if let imView = tapped.view?.subviews[1] as? UIImageView{
if imView.isKind(of: UIImageView.self){
if items[tapped.view!.tag].collapsed{
imView.image = UIImage(named: "collapsed")
}else{
imView.image = UIImage(named: "expand")
}
}
}
tableView.reloadData()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = items[indexPath.row]
print("IndexPath :- \(row.name)")
}
}
If you look at the way you are setting the text in your cells in cellForRowAt:
cell.textLabel?.text = items[indexPath.section].items[indexPath.row]
You are saying:
get the ItemList object for indexPath.section from items array
get the String from that object's items array of this indexPath.row
However, in your didSelectRowAt:
let row = items[indexPath.row]
print("IndexPath :- \(row.name)")
You are saying:
get the ItemList object for indexPath.row from items array
print the .name property of that object
So, change your didSelectRowAt code to match your cellForRowAt logic:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedString = items[indexPath.section].items[indexPath.row]
print("IndexPath :- \(selectedString)")
// or, for a little more clarity
let sectionObject = items[indexPath.section]
let rowItem = sectionObject.items[indexPath.row]
print("IndexPath :- \(indexPath) // Section :- \(sectionObject.name) // Item :- \(rowItem)")
}

TableView Collapse, why it's sticking up like this?

I'm setting up a collapsable tableView, but something strange happens on the collapsable item. When you look at the video keep an eye on the "Where are you located" line.. (I'm using a .plist for the question and answer items)
Where do I go wrong, is it somewhere in my code? I don't want to let that line stick on the top :(
Here is the code I'm using but I can't find anything strange...
class FAQViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var questionsArray = [String]()
var answersDict = Dictionary<String, [String]>() // multiple answers for a question
var collapsedArray = [Bool]()
#IBOutlet weak var tableView: UITableView!
override func viewWillAppear(_ animated: Bool) {
// Hide the navigation bar on the this view controller
self.navigationController?.setNavigationBarHidden(true, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
addTableStyles()
readQAFile()
tableView.delegate = self
tableView.dataSource = self
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
func addTableStyles(){
navigationController?.isNavigationBarHidden = false
self.tableView?.backgroundView = {
let view = UIView(frame: self.tableView.bounds)
return view
}()
tableView.estimatedRowHeight = 43.0;
tableView.rowHeight = UITableView.automaticDimension
tableView.separatorStyle = UITableViewCell.SeparatorStyle.singleLine
}
func readQAFile(){
guard let url = Bundle.main.url(forResource: "QA", withExtension: "plist")
else { print("no QAFile found")
return
}
let QAFileData = try! Data(contentsOf: url)
let dict = try! PropertyListSerialization.propertyList(from: QAFileData, format: nil) as! Dictionary<String, Any>
// Read the questions and answers from the plist
questionsArray = dict["Questions"] as! [String]
answersDict = dict["Answers"] as! Dictionary<String, [String]>
// Initially collapse every question
for _ in 0..<questionsArray.count {
collapsedArray.append(false)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return questionsArray.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if collapsedArray[section] {
let ansCount = answersDict[String(section)]!
return ansCount.count
}
return 0
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
// Set it to any number
return 70
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 1
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if collapsedArray[indexPath.section] {
return UITableView.automaticDimension
}
return 2
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRect(x:0, y:0, width:tableView.frame.size.width, height:40))
headerView.tag = section
let headerString = UILabel(frame: CGRect(x: 10, y: 10, width: tableView.frame.size.width, height: 50)) as UILabel
headerString.text = "\(questionsArray[section])"
headerView .addSubview(headerString)
let headerTapped = UITapGestureRecognizer (target: self, action:#selector(sectionHeaderTapped(_:)))
headerView.addGestureRecognizer(headerTapped)
return headerView
}
#objc func sectionHeaderTapped(_ recognizer: UITapGestureRecognizer) {
let indexPath : IndexPath = IndexPath(row: 0, section:recognizer.view!.tag)
if (indexPath.row == 0) {
let collapsed = collapsedArray[indexPath.section]
collapsedArray[indexPath.section] = !collapsed
//reload specific section animated
let range = Range(NSRange(location: indexPath.section, length: 1))!
let sectionToReload = IndexSet(integersIn: range)
self.tableView.reloadSections(sectionToReload as IndexSet, with:UITableView.RowAnimation.fade)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cellIdentifier = "Cell"
let cell: UITableViewCell! = self.tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
cell.textLabel?.adjustsFontSizeToFitWidth = true
cell.textLabel?.numberOfLines = 0
let manyCells : Bool = collapsedArray[indexPath.section]
if (manyCells) {
let content = answersDict[String(indexPath.section)]
cell.textLabel?.text = content![indexPath.row]
}
return cell
}
override var prefersStatusBarHidden: Bool {
return true
}
}
You need to change the style of the tableView to grouped, when you initialize it:
let tableView = UITableView(frame: someFrame, style: .grouped)
or from Storyboard:
After that you will have this issue, which I solved by setting a tableHeaderView to the tableView that has CGFloat.leastNormalMagnitude as its height:
override func viewDidLoad() {
super.viewDidLoad()
var frame = CGRect.zero
frame.size.height = .leastNormalMagnitude
tableView.tableHeaderView = UIView(frame: frame)
}
Just remove your headerView from view hierarchy here
#objc func sectionHeaderTapped(_ recognizer: UITapGestureRecognizer) {
headerView.removeFromSuperview()
...
}
By the way, yes creating a openable tableview menu with using plist is one of the methods but it could be more simple. In my opinion you should refactor your code.

SwiftUI Custom UITableView Representable contentOffset is always wrong on the last elements

I made a custom UITableView to be implemented with SwiftUI, to customize the header view and section headers. Every item is written in SwiftUI, and has a set height. The table is wrapped inside a GeometryReader.
I need to save the scroll offset while navigating between pages, so everytime I tap on an item, I save the contentOffset in an #ObservableObject, and when navigating back to that view, I just pass the saved offset (I'm not using the standard NavigationLink navigation, but a custom stack, so it is not saved between pages).
The problem is that, whenever the UITableView content is loaded with a previously set contentOffset (which is (x:0; y:0) by default), the content shown is always the previous content (i.e. if I have 14 rows and I tap on row 14, the setContentOffset only shows rows up to row 8/9).
This doesn't happen if I tap on the first rows, like 5 or 6.
I've already tried different solutions, like setting a height dictionary for rows, saving their height and passing it to the delegate methods, but it doesn't work.
Also layoutIfNeeded(), applied to the UITableView during the makeUIView doesn't do anything.
I currently can't set automaticallyAdjustScrollViewInsets = false because
I would have to rewrite the entire component to fit in a UIViewController
The contentInset is already always zero, which I think is the purpose of that instruction.
What I've noticed though, is that my UITableViewRepresentable inside the GeometryReader is drawn twice. I'm not sure why, but it just happens. Only the second time, the containerSize is different than zero.
This is my code:
UITableViewRepresentable
import SwiftUI
import UIKit
struct UITableViewRepresentable: UIViewRepresentable {
var sections: [String]
var items: [Int:[AnyView]]
var tableHeaderView: AnyView? = nil
var separatorStyle: UITableViewCell.SeparatorStyle = .singleLine
var separatorInset: UIEdgeInsets?
var scrollOffset: CGPoint
var onTap: (CGPoint) -> Void
var sectionHorizontalPadding: CGFloat = 5
var sectionHeight: CGFloat = 50
var containerSize: CGSize
func makeUIView(context: Context) -> UITableView {
assert(items.count > 0)
let uiTableView = UITableView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: self.containerSize), style: .plain)
uiTableView.sizeToFit()
uiTableView.separatorStyle = self.separatorStyle
if(self.separatorStyle == .singleLine && self.separatorInset != nil) {
uiTableView.separatorInset = self.separatorInset!
}
uiTableView.automaticallyAdjustsScrollIndicatorInsets = false
uiTableView.dataSource = context.coordinator
uiTableView.delegate = context.coordinator
if(tableHeaderView != nil) {
let hostingHeader: UIHostingController = UIHostingController<AnyView>(rootView: tableHeaderView!)
uiTableView.tableHeaderView = hostingHeader.view
uiTableView.tableHeaderView!.sizeToFit()
}
uiTableView.register(HostingCell.self, forCellReuseIdentifier: "Cell")
return uiTableView
}
func updateUIView(_ uiTableView: UITableView, context: Context) {}
func makeCoordinator() -> Coordinator {
return Coordinator(self, sectionHeight: self.sectionHeight)
}
class HostingCell: UITableViewCell { // just to hold hosting controller
var host: UIHostingController<AnyView>?
}
class Coordinator: NSObject, UITableViewDelegate, UITableViewDataSource {
var parent: UITableViewRepresentable
var sectionHeight: CGFloat
var scrollOffset: CGPoint
var alreadyScrolled: Bool
init(_ parent: UITableViewRepresentable, sectionHeight: CGFloat) {
self.parent = parent
self.sectionHeight = sectionHeight
self.scrollOffset = self.parent.scrollOffset
self.alreadyScrolled = false
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.parent.items.keys.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return parent.items[section]?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! HostingCell
let view = self.parent.items[indexPath.section]![indexPath.row]
// create & setup hosting controller only once
if tableViewCell.host == nil {
let controller = UIHostingController(rootView: AnyView(view))
tableViewCell.host = controller
let tableCellViewContent = controller.view!
tableCellViewContent.translatesAutoresizingMaskIntoConstraints = false
tableViewCell.contentView.addSubview(tableCellViewContent)
tableCellViewContent.topAnchor.constraint(equalTo: tableViewCell.contentView.topAnchor).isActive = true
tableCellViewContent.leftAnchor.constraint(equalTo: tableViewCell.contentView.leftAnchor).isActive = true
tableCellViewContent.bottomAnchor.constraint(equalTo: tableViewCell.contentView.bottomAnchor).isActive = true
tableCellViewContent.rightAnchor.constraint(equalTo: tableViewCell.contentView.rightAnchor).isActive = true
} else {
// reused cell, so just set other SwiftUI root view
tableViewCell.host?.rootView = AnyView(view)
}
tableViewCell.layoutIfNeeded()
return tableViewCell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.scrollOffset = tableView.contentOffset
self.parent.onTap(self.scrollOffset)
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if(sectionHeight == 0) {
return nil
}
let headerView = UIView(
frame: CGRect(
x: 0,
y: 0,
width: tableView.frame.width,
height: sectionHeight
)
)
headerView.backgroundColor = App.Colors.NumberIcon.MainColor_UI
let label = UILabel()
label.frame = CGRect.init(
x: self.parent.sectionHorizontalPadding,
y: headerView.frame.height / 2,
width: headerView.frame.width,
height: headerView.frame.height / 2
)
label.text = self.parent.sections[section].uppercased()
label.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.footnote).bold()
label.textColor = .white
headerView.addSubview(label)
return headerView
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return sectionHeight
}
fileprivate var heightDictionary: [Int : CGFloat] = [:]
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
heightDictionary[indexPath.row] = cell.frame.size.height
// if the first row has been drawed, then the content is ready, and the UITableView can scroll
if let _ = tableView.indexPathsForVisibleRows?.first, self.scrollOffset.y != 0 {
if indexPath.row == 0 && !self.alreadyScrolled {
tableView.setContentOffset(self.scrollOffset, animated: false)
self.alreadyScrolled = true // to prevent further updates of redeclarations of Coordinator
}
}
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
let height = heightDictionary[indexPath.row]
return height ?? UITableView.automaticDimension
}
}
}
And this is my ContentView
struct ContentView: View {
#ObservedObject var listData: ListData = ListData()
var body: some View {
GeometryReader { geometry -> AnyView in
let tableHeaderView = AnyView(Text("TableHeaderView"))
let itemHeight: CGFloat = geometry.size.height * 1/3
let items:[AnyView] = [AnyView(Text("Item 1").frame(height: itemHeight)), AnyView(Text("Item 2").frame(height: itemHeight))]
return UITableViewRepresentable(
sections: ["Section 1"],
items: [0:items],
tableHeaderView: tableHeaderView,
separatorStyle: .none,
scrollOffset: self.listData.scrollOffset,
onTap: { (scrollOffset) in
self.listData.scrollOffset = scrollOffset
// navigate to other page...
},
sectionHorizontalPadding: itemHorizontalPadding,
containerSize: CGSize(width: pageWidth, height: listHeight)
).frame(width: geometry.size.width, height: geometry.size.height * 0.9)
}
}
}
ListData just holds the scrollOffset
class ListData: ObservableObject {
#Published var scrollOffset: CGPoint = CGPoint(x:0, y:0)
}
I don't understand this behaviour, but I'm also a beginner of UIKit, so I don't know if it's intended or not. Any help is much appreciated.
In the end I had to resort to the UIScrollView.contentOffset property, which is correct 100% of the time.
Updated code:
func updateUIView(_ uiTableView: UITableView, context: Context) {
if(!context.coordinator.alreadyScrolled) {
uiTableView.layoutIfNeeded()
Utilities.Threading.UI {
// remove animations so it doesn't do the scrolling animation during the begin/endUpdates, it can be omitted if you like
UIView.performWithoutAnimation {
uiTableView.beginUpdates()
uiTableView.setContentOffset(self.scrollOffset, animated: false)
uiTableView.endUpdates()
}
context.coordinator.alreadyScrolled = true
}
}
}
and in the Coordinator
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.scrollIndex = indexPath
self.parent.onTap(indexPath, self.scrollOffset)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.scrollOffset = scrollView.contentOffset
}
I've also removed the code inside the willDisplayCell delegate method that scrolls automatically.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
heightDictionary[indexPath.row] = cell.frame.size.height
}

Deleting a custom view from UITableView

I am swiping left to delete a cell that is a customview in Swift 3.
The cell is:
class CustomTableCell: SwipeTableViewCell
{
public var atest = UILabel();
public var btest = UILabel();
var animator: Any?
override init(style: UITableViewCellStyle, reuseIdentifier: String!)
{
super.init(style: style, reuseIdentifier: reuseIdentifier);
let height = 140;
atest = UILabel(frame: CGRect(x: 20 + (activityWidth / 2), y: 72, width: (activityWidth / 2) - 10, height: 30));
btest = UILabel(frame: CGRect(x: 20 + (activityWidth / 2), y: 102, width: (activityWidth / 2) - 10, height: 30));
self.contentView.addSubview(atest);
self.contentView.addSubview(btest);
}
Then in my table view controller I have:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") as! CustomTableCell;
cell.atest.text = "text from an array at indexpath.row";
cell.btest.text = "another different text from an array";
return cell;
}
The deletion in the table view controller happens here:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]?
{
let delete = SwipeAction(style: .destructive, title: "Delete")
{
action, indexPath in
print("delete button tapped");
// self.table.deleteRows(at: [indexPath], with: .none);
// database is a string index array
self.database.remove(at: indexPath.row);
if (self.database.count == 0)
{
self.noText.isHidden = false;
self.footer.isHidden = false;
self.table.tableFooterView = self.footer;
}
// self.table.setNeedsDisplay();
}
delete.backgroundColor = UIColor.red;
return [delete];
I do a delete by swiping left and it deletes everything correctly. The issue I have is that the deleting makes all the table view cells under the fold the same as the last visible cell.
How do I fix this? I am using a custom cell view too.
An example is that I have 6 rows and the top 4 are visible. Deleting the first row makes the 4th and 5th rows the same. As in the last visible row also becomes the first none visible row. The prepareForReuse is probably not working right.
The delete works and goes from 6 rows to 5 but an example is below.
UITableView
First row label A
Second row label B
Third row label C
Fourth row label D (last visible row)
Fifth row label E (first non visible row)
Sixth row label F
Deleting the first row by swiping creates this new UITableView:
UITableView
Second row label B
Third row label C
Fourth row label D
Fourth row label D (last visible row)
Fifth row label E (first non visible row)
The reusable cells are not working correctly.
I do not use awakeFromNib and just upgraded to swift 4.1 as well.
In TableView custom cells if you are adding views from storyboard or XIB then it get removed on scrolling but if you are adding views programmatically then you have to remove the view from the tableViewCell:
Either using below code in cellForRow :
for label in cell.subviews {
if let mylabel = label as? UILabel {
mylabel.removeFromSuperview()
}
}
or you can use this code customCellClass in prepareForReuse method:
class myCustomCell: UITableViewCell {
override func prepareForReuse() {
super.prepareForReuse()
for view in self.subviews {
view.removeFromSuperview()
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
Above code will remove all subviews from the cell.
Check below links for more answers:
remove the subviews from the contentView of UITableViewCell (reset the UITableView)
EDIT
I tried below code with string Array and swipeDelete functionality in tableView:
class ViewController: UIViewController {
#IBOutlet weak var sampleTableView: UITableView!
var nameArray = ["Snoop", "Sarah", "Fido", "Mark", "Jill", "Parague", "London", "Barcelona", "Italy", "France", "Eiffiel", "Tower", "Paris", "Europe", "Amsterdam", "Zurich", "Germany", "Munich", "Milan", "Venice", "Switzerland", "Brussels"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: indexPath)
cell.textLabel?.text = nameArray[indexPath.row] + " - " + String(describing: indexPath.row)
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
// handle delete (by removing the data from your array and updating the tableview)
tableView.beginUpdates()
nameArray.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
}
}
When I start swipe deleting the tableView cells the string data is correct but the indexPath value doesn't change. It changes when I scrolled the tableView. Which is correct logically because after deletion the string data indexes have changed and when cells are reused the new index will be visible.
EDIT Using SwipwCellKit:
Used the same code of yours with the SwipeCellKit:
My ViewController :
class ViewController: UIViewController {
#IBOutlet weak var sampleTableView: UITableView!
var nameArray = ["Snoop", "Sarah", "Fido", "Mark", "Jill", "Parague", "London", "Barcelona", "Italy", "France", "Eiffiel", "Tower", "Paris", "Europe", "Amsterdam", "Zurich", "Germany", "Munich", "Milan", "Venice", "Switzerland", "Brussels"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
extension ViewController : UITableViewDataSource, UITableViewDelegate, SwipeTableViewCellDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 72
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") as! CustomTableCell
cell.delegate = self
cell.atest.text = nameArray[indexPath.row] + " - " + String(describing: indexPath.row)
cell.btest.text = "another different text from an array";
return cell;
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]?
{
let delete = SwipeAction(style: .destructive, title: "Delete")
{
action, indexPath in
print("delete button tapped")
self.nameArray.remove(at: indexPath.row)
self.sampleTableView.deleteRows(at: [indexPath], with: .none)
}
delete.backgroundColor = UIColor.red;
return [delete];
}
}
My CustomTableViewCell:
class CustomTableCell: SwipeTableViewCell {
public var atest = UILabel()
public var btest = UILabel()
var animator: Any?
override func awakeFromNib() {
atest = UILabel(frame: CGRect(x: 20 , y: 2, width: 200, height: 30));
btest = UILabel(frame: CGRect(x: 20 , y: 32, width: 200, height: 30));
self.contentView.addSubview(atest);
self.contentView.addSubview(btest);
}
}
It is working same as the first editing.
EDIT
CustomTableViewCell using init :
class CustomTableViewCell: SwipeTableViewCell {
public var atest = UILabel();
public var btest = UILabel();
var animator: Any?
override init(style: UITableViewCellStyle, reuseIdentifier: String!)
{
super.init(style: style, reuseIdentifier: reuseIdentifier)
atest = UILabel(frame: CGRect(x: 20 , y: 2, width: 100, height: 30))
btest = UILabel(frame: CGRect(x: 20 , y: 32, width: 100, height: 30))
self.contentView.addSubview(atest)
self.contentView.addSubview(btest)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
fatalError("init(coder:) has not been implemented")
}
}
change in cellForRow:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = CustomTableViewCell.init(style: .default, reuseIdentifier: "reuseIdentifier")
cell.delegate = self
cell.atest.text = nameArray[indexPath.row] + " - " + String(describing: indexPath.row)
cell.btest.text = "another different text from an array";
return cell;
}
still working same as above.
The problem is in how your cells are being reused. Add this line of code to your table cell class:
override func prepareForReuse() {
super.prepareForReuse()
atest.text = nil
btest.text = nil
}

UITableview scrolling is not responding properly?

booktable.frame = CGRect(x: 0, y: booktopview.bounds.height, width: screenWidth, height: screenHeight-booktopview.bounds.height-tabbarView.bounds.height)
booktable.register(UITableViewCell.self, forCellReuseIdentifier: "mycell")
booktable.dataSource = self
booktable.delegate = self
booktable.separatorColor = UIColor.lightGray
booktable.backgroundColor = UIColor.clear
booktable.separatorStyle = .singleLine
bookview.addSubview(booktable)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if(tableView == booktable)
{
let cell1 = booktable.dequeueReusableCell(withIdentifier: "mycell")
for object in (cell1?.contentView.subviews)!
{
object.removeFromSuperview();
}
let img :UIImageView = UIImageView()
let lbl : UILabel = UILabel()
img.frame = CGRect(x: 15, y: 15, width: 80, height: 130)
img.image = imgarray[indexPath.row]
img.layer.borderWidth = 1.0
img.layer.borderColor = UIColor.lightGray.cgColor
cell1?.contentView.addSubview(img)
imgheight = img.bounds.height
lbl.frame = CGRect(x: img.bounds.width + 40, y: (imgheight+40-80)/2, width: booktable.bounds.width-img.bounds.width + 40 - 100, height: 80)
lbl.text = imgname[indexPath.row]
lbl.numberOfLines = 0
lbl.textAlignment = .left
lbl.font = UIFont(name: "Arial", size: 23)
lbl.textColor = UIColor.black
cell1?.selectionStyle = .none
cell1?.contentView.addSubview(lbl)
return cell1!
}
The code shown above is for book table, which sometimes scrolls like normal and sometimes not scrolling at all. I am doing all the code programatically. I have tested this on both simulators and devices but still the problem exists. Any help is appreciated...
Create Custom UITableViewCell, let's say it is ListTableCell
class ListTableCell: UITableViewCell {
#IBOutlet weak var lblTemp: UILabel!
#IBOutlet weak var imgTemp: UIImage!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I've created UITableViewCell with xib like this and bind IBOutlets
Let's say we have struct Model and array like this
struct Model {
let image : UIImage
let name: String
}
for i in 0...10 {
let model = Model(image: #imageLiteral(resourceName: "Cat03"), name: "Temp \(i)")
array.append(model)
}
Now on ViewController viewDidLoad() method,
tableView.register(UINib(nibName: "ListTableCell", bundle: nil), forCellReuseIdentifier: "ListTableCell")
Implement UITableViewDataSource methods like this,
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ListTableCell") as! ListTableCell
let model = array[indexPath.row]
cell.lblTemp.text = model.name
cell.imgTemp.image = model.image
return cell
}
FYI
For different tableviews, you can create different custom cell the same way and cellForRowAt indexPath and numberOfRowsInSection method will change appropriately.
Let me know in case of any queries.
UPDATE
Follow this and this to create CustomTableCell programmatically

Resources