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

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
}

Related

iOS: UI Distortion when using SwiftUI views in UIKit TableView [duplicate]

I am trying to use UITableView in a SwiftUI app
struct UIList: UIViewRepresentable {
var rows: [String]
func makeUIView(context: Context) -> UITableView {
let collectionView = UITableView(frame: .zero, style: .plain)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.dataSource = context.coordinator
collectionView.delegate = context.coordinator
collectionView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
return collectionView
}
func updateUIView(_ uiView: UITableView, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(rows: rows)
}
class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
var rows: [String]
init(rows: [String]) {
self.rows = rows
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
self.rows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) //as! AlbumPrivateCell
let view = Text(rows[indexPath.row]).frame(height: 50).background(Color.blue)// UIFactory(appComponent:
let controller = UIHostingController(rootView: view)
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
return tableViewCell
}
}
}
When I scroll the table quickly the cells content get a random padding at top and bottom of each cell, any idea why this happens ?
PS: I know I could use List , I try to use UITableView because I have to add multiple swipe actions, but List only allows one swipe action (delete)
I assume this is due to corrupted reused table view cells... (and probably lost hosting controllers, because there where created on stack and not stored anywhere)
Please find below corrected a bit your code with mentioned fixes. Tested & worked with Xcode 11.2 / iOS 13.2.
Here is code (with some comments inline):
class HostingCell: UITableViewCell { // just to hold hosting controller
var host: UIHostingController<AnyView>?
}
struct UIList: UIViewRepresentable {
var rows: [String]
func makeUIView(context: Context) -> UITableView {
let collectionView = UITableView(frame: .zero, style: .plain)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.dataSource = context.coordinator
collectionView.delegate = context.coordinator
collectionView.register(HostingCell.self, forCellReuseIdentifier: "Cell")
return collectionView
}
func updateUIView(_ uiView: UITableView, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(rows: rows)
}
class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
var rows: [String]
init(rows: [String]) {
self.rows = rows
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
self.rows.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! HostingCell
let view = Text(rows[indexPath.row])
.frame(height: 50).background(Color.blue)
// 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.setNeedsLayout()
return tableViewCell
}
}
}
Added demo code for just UIList itself - works fine with pro models as well.
struct TestUIList: View {
var body: some View {
UIList(rows: generateRows())
}
func generateRows() -> [String] {
(0..<100).reduce([]) { $0 + ["Row \($1)"] }
}
}

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.

Collapse other UITableViewCell when current cell is expanded

I am trying to expand my UITableViewCell and I can expand cells. But I want to collapse the UITableViewCell which are not selected.
What I am trying in code:
var expandedCells = [Int]()
#IBOutlet weak var tableVieww:UITableView!
#IBAction func buttonPressed(_ sender: AnyObject) {
// If the array contains the button that was pressed, then remove that button from the array
if expandedCells.contains(sender.tag) {
expandedCells = expandedCells.filter({ $0 != sender.tag})
}
// Otherwise, add the button to the array
else {
expandedCells.append(sender.tag)
}
// Reload the tableView data anytime a button is pressed
tableVieww.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! exampleCell
cell.myButton.tag = indexPath.row
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Set the row height based on whether or not the Int associated with that row is contained in the expandedCells array
if expandedCells.contains(indexPath.row) {
return 212
} else {
return 57
}
}
You can maintain a variable for maintaining the selected index as below,
var expandedIndexPath: IndexPath?
Then update your tableView delegate as follows,
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
expandedIndexPath = indexPath // Update the expandedIndexPath with the selected index
tableView.reloadData() // Reload tableview to reflect the change
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Check wether expanded indexpath is the current index path and updated return the respective height
if expandedIndexPath == indexPath {
return 212
} else {
return 57
}
}
This should work fine.
1) First Create Bool Array variable
var isExpandArr = Array<Bool>()
2) Insert true at 0 index in ViewLoad()
isExpandArr.insert(true, at: 0)
3) Put Into cellForRowAt
cell.ToggleBT.addTarget(self, action: #selector(handleToggleBtn), for:.touchUpInside)
cell.ToggleBT.tag = indexPath.row
if isExpandArr[indexPath.row] == true{
cell.bottomrView.isHidden = false
}else{
cell.bottomrView.isHidden = true
}
4) set heightForRowAt
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return isExpandArr[indexPath.row] ? 204 : 60
}
5) put function
func handleToggleBtn(sender: UIButton){
print(sender.tag)
if isExpandArr.contains(true){
print("yes")
if let unwrappedIndex = isExpandArr.index(of: true){
isExpandArr[unwrappedIndex] = false
isExpandArr[sender.tag] = true
}
}
table.reloadData()
}
in #swift 2.3
Hope this will also helpful
For collapse and expand the header
define the variable above the viewDidLoad or within the class block
var headerIcon: UIImageView = UIImageView()
var sectionTitleArray : NSMutableArray = NSMutableArray()
var sectionContentEventTitleDict : NSMutableDictionary = NSMutableDictionary()
var sectionEventImagesDict : NSMutableDictionary = NSMutableDictionary()
var arrayForBool : NSMutableArray = NSMutableArray()
var arrFirstSectionItemTitle = ["Section 1 item Name 1","Section 1 item Name 2","Section 1 item Name 3","Section 1 item Name 4","Section 1 item Name 5","Section 1 item Name 6","Section 1 item Name 7"]
var arrFirstSectionItemImages = ["Section_1_icon_1","Section_1_icon_2","Section_1_icon_3","Section_1_icon_4","Section_1_icon_5","Section_1_icon_6","Section_1_icon_7"]
var arrSecondSectionItemTitle = ["Section 2 item Name 1","Section 2 item Name 2","Section 2 item Name 3","Section 2 item Name 4"]
var arrSecondSectionItemImages = ["Section_1_icon_1","Section_2_item_icon_2","Section_2_item_icon_3","Section_1_item_icon_4","Section_1_item_icon_5","Section_1_item_icon_6","Section_1_item_icon_7"]
var arrData: Array<Dictionary<String,AnyObject>> = Array<Dictionary<String,AnyObject>>()
#IBOutlet weak var tableList: UITableView!
in viewDidLoad() method write these lines of code
tableList.delegate = self
tableList.dataSource = self
tableList.backgroundColor = UIColor.getRGBColor(240, g: 240, b: 240)
let customView = UIView(frame: CGRectMake(0, 0, 200, 80))
customView.backgroundColor = UIColor.getRGBColor(240, g: 240, b: 240)
let customLine = UIView(frame: CGRectMake(0, 0, tblAllQuestion.frame.size.width, 1))
customLine.backgroundColor = UIColor.getRGBColor(240, g: 240, b: 240)
customView.addSubview(customLine)
tableList.tableFooterView = customView
now write the delegate and datasource methods as following:
//MARK: UITableViewDelegate AND UITableViewDataSource METHOD WITH SECTION
for number of sections to be collapsed and expand
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
print_debug(sectionTitleArray.count)
return sectionTitleArray.count
}
cell method:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CELL_IDENTIFIER", forIndexPath: indexPath)as! CellAllQuestionFiqhVC
//let cellIdentifier = "CellAllQuestionFiqhVC"
// cell = self.tableList.dequeueReusableCellWithIdentifier(cellIdentifier)
let manyCells : Bool = arrayForBool .objectAtIndex(indexPath.section).boolValue
if (!manyCells) {
// cell.textLabel.text = #"click to enlarge";
}
else{
let group = self.arrData[indexPath.section]
let data: Array<Dictionary<String,AnyObject>> = group["data"]as! Array<Dictionary<String,AnyObject>>
cell.configCell(data[indexPath.row], instance: self)
}
return cell
}
number cell in a section:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(arrayForBool.objectAtIndex(section).boolValue == true){
let count1 = arrData[section]["data"]!
if count1.count != nil {
return count1.count
}
}
return 0;
}
the height of the section
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 40
}
The height of the cell:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(arrayForBool.objectAtIndex(indexPath.section).boolValue == true){
return 100
}
return 2;
}
to set header tappable:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 40))
headerView.backgroundColor = UIColor.whiteColor()//Constant.kAPP_COLOR
headerView.tag = section
let headerString = UILabel(frame: CGRectMake(40/*tableView.frame.size.width/2-30*/, 10, tableView.frame.size.width, 18))
let headerLine = UIView(frame: CGRectMake(0, 39, tableView.frame.size.width, 1))
//UILabel(frame: CGRect(x: 20, y: 10, width: tableView.frame.size.width-10, height: 30)) as UILabel
//headerLine.text = sectionTitleArray.objectAtIndex(section) as? String
headerLine.backgroundColor = UIColor.getRGBColor(240, g: 240, b: 240)
headerView .addSubview(headerLine)
headerIcon = UIImageView(frame: CGRect(x: tblAllQuestion.bounds.maxX-30, y: 10, width: 8, height: 12)) as UIImageView
headerIcon.image = UIImage(named: "right_arrow")
headerView.addSubview(headerIcon)
let headerTapped = UITapGestureRecognizer(target: self, action: #selector(sectionHeaderTapped))
headerView.addGestureRecognizer(headerTapped)
return headerView
}
for rotate section icon on tapped on:
func rotateSectionIconOnTapped(indexTapped: Int) {
isActiveMap = false
if arrayForBool.objectAtIndex(indexTapped) as! NSObject == true {
print("toggle the icon at degree 90")
headerIcon.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2))
}
else if arrayForBool.objectAtIndex(indexTapped) as! NSObject == false {
print("toggle the ico at degree 0")
headerIcon.transform = CGAffineTransformMakeRotation(0)
}
}
action for section header tapped:
func sectionHeaderTapped(recognizer: UITapGestureRecognizer) {
//filterByCategoryIsOn = false
print("Tapping working")
print(recognizer.view?.tag)
//if recognizer.view!.tag != 2 {
let indexPath : NSIndexPath = NSIndexPath(forRow: 0, inSection:(recognizer.view?.tag as Int!)!)
if (indexPath.row == 0) {
for (indexx, bl) in arrayForBool.enumerate() {
if recognizer.view!.tag != indexx {
let index : NSIndexPath = NSIndexPath(forRow: 0, inSection:(indexx))
arrayForBool.replaceObjectAtIndex(index.section, withObject: false)
let range = NSMakeRange(index.section, 1)
let sectionToReload = NSIndexSet(indexesInRange: range)
self.tableList.reloadSections(sectionToReload, withRowAnimation:UITableViewRowAnimation.Fade)
}
}
var collapsed = arrayForBool.objectAtIndex(indexPath.section).boolValue
print_debug(collapsed)
collapsed = !collapsed;
print_debug(collapsed)
arrayForBool.replaceObjectAtIndex(indexPath.section, withObject: collapsed)
print(arrayForBool)
//reload specific section animated
let range = NSMakeRange(indexPath.section, 1)
let sectionToReload = NSIndexSet(indexesInRange: range)
self.tableList.reloadSections(sectionToReload, withRowAnimation:UITableViewRowAnimation.Fade)
rotateSectionIconOnTapped(recognizer.view!.tag)
}
}
get action for didselect row at index path :
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print_debug(indexPath)
print_debug(indexPath.row)
print_debug(indexPath.section)
if indexPath.section == 0 {
}
else if indexPath.section == 1 {
}
}

Spritekit - Tableview scrolling cut off

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)

When I swipe a UITableViewCell, the header view moves as well

So, I have a few Swipe actions like delete, block, etc in my UITableView. I wanted to add headers to separate my two sections. So, I added a prototype cell, named it HeaderCell and then went to the view. I added one label, named headerLabe. My problem is that when I swipe for the actions, the header cells were moving as well, which looked bad. I researched, and found a solution to just return the contentView of the cell. However, when I do this, the label has not shown up. I have tried a dozen different solutions, and nothing has worked, so I have turned to SO. Can anyone help me?
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell : CustomHeaderTableViewCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! CustomHeaderTableViewCell
if section == 0 {
headerCell.headerLabel.text = "Thank You's"
} else if section == 1 {
headerCell.headerLabel.text = "Conversations"
}
return headerCell.contentView
}
Thanks so much.
You can use a section Header as #ozgur suggest.If you still want to use a cell.
Refer to this datasource method
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if indexPath = YourHeaderCellIndexPath{
return false
}
return true
}
check the following methods
In your UIViewController use the following
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("HeaderCell") as! WishListHeaderCell
headerCell.lblTitle.text = cartsData.stores_Brand_Name
let imgVw = UIImageView()
imgVw.frame = CGRectMake(8, 18, 25, 25)
imgVw.image = UIImage(named: "location.png")
let title = UILabel()
title.frame = CGRectMake(41, 10, headerCell.viwContent.frame.width - 49, 41)
title.text = cartsData.stores_Brand_Name
title.textColor = UIColor.whiteColor()
headerCell.viwContent.addSubview(imgVw)
headerCell.viwContent.addSubview(title)
return headerCell.viwContent
}
In your UITableViewCell use the following
import UIKit
class HeaderCell: UITableViewCell {
#IBOutlet weak var viwContent: UIView!
#IBOutlet weak var imgIcn: UIImageView!
#IBOutlet weak var lblTitle: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
self.viwContent.backgroundColor = UIColor.grayColor()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//UITableViewCell
let headerCell = tableView.dequeueReusableCellWithIdentifier("headerCell") as! SecJobCCHeaderTableViewCell
// Cell Rect
var cellRect : CGRect = headerCell.frame
cellRect.size.width = screenBounds.width
// Header Footer View
let headerFooterView = UITableViewHeaderFooterView(frame : cellRect)
//Adding Gesture
let swipeGestRight = UISwipeGestureRecognizer(target: self, action:#selector(AddSecJobCostCentreViewController.draggedViewRight(_:)))
swipeGestRight.enabled = true
swipeGestRight.direction = UISwipeGestureRecognizerDirection.Right
headerFooterView.addGestureRecognizer(swipeGestRight)
// Update Cell Rect
headerCell.frame = cellRect
// Add Cell As Subview
headerCell.tag = 1000
headerFooterView.addSubview(headerCell)
// Return Header Footer View
return headerFooterView
}
func draggedViewRight(sender:UISwipeGestureRecognizer) {
// Swipe Gesture Action
let currentHeaderView = sender.view?.viewWithTag(1000) as! SecJobCCHeaderTableViewCell
}

Resources