UITableView Separators and Disclosure Indicator missing after altering UITableViewCellActionButton - ios

with the help of this Thread I managed to alter the font of my UITableViewCellActionButtons, but now the line-separator for each cell only gets visible after swiping the cell once and the disclosure indicators never show up.
Here is my extension-code:
extension UITableViewCell{
override open func layoutSubviews() {
super.layoutSubviews()
for subview in self.subviews {
for sub in subview.subviews {
if String(describing: sub).range(of: "UITableViewCellActionButton") != nil {
for view in sub.subviews {
if String(describing: view).range(of: "UIButtonLabel") != nil {
if let label = view as? UILabel {
label.font = UIFont(name: "CaptureSmallz", size: label.font.pointSize)
}
}
}
}
}
}
}
}
My cellforRowAt looks like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "planNameCell", for: indexPath)
...
cell.textLabel?.text = ...
cell.detailTextLabel?.text = ...
cell.selectionStyle = UITableViewCellSelectionStyle.none
let textLabelFontSize = cell.textLabel?.font.pointSize
cell.textLabel?.font = UIFont(name: "CaptureSmallz", size: textLabelFontSize!)!
cell.textLabel?.sizeToFit()
let detailLabelFontSize = cell.detailTextLabel?.font.pointSize
cell.detailTextLabel?.font = UIFont(name: "CaptureSmallz", size: detailLabelFontSize!)!
cell.detailTextLabel?.sizeToFit()
return cell
}
I hope somebody knows what I messed up here.
Thanks for the help!
//Edit 1:
I've checked the view hierachy with the Xcode-Debugger, but it shows only a blank screen. But I can see the hierachy on the left side:
see this screenshot
// this is the state of the app I debugged
I noticed that there arent any views for the disclosure indicators and when I swipe left on a cell once the line separator appears and one _UITableViewCellSeparatorView entry gets added in the view hierachy. But I still don't know where to look now.
//Edit 2:
I'm currently trying to get rid of the extension and call the code in it manually, but I can`t figure it out where to put it. I tried "viewDidAppear", but then the "UITableViewCellActionButton" aren't there yet. Is there a way to call function right after "editActionsForRowAt" has been called?

Related

Expanding Custom TableviewCell not working properly and displays incorrect view

I have a table view controller with a section cell and on click it expands and other subsections are showed. A section cell will always have title and a button and it may or may not have a description, on expanding the cell with no description, I am applying a centerYanchor on the title of the section cell so that it's aligned accordingly to the expand icon.
On expanding the cells which have a description it works as expected, also the section with no description has centerYanchor applied to it and works properly.
Now the problem that I am facing is as soon as I expand a cell with no description, the cells with description starts to behave weirdly on expanding.
As you can see the first two cells with description opened properly and other cells with no description is also aligned with the button.
In this case I opened the third cell first and on opening the first cell, even though it had description the centerYanchor and hiden logic is being applied to it.
Here is the code for tableViewController
override func numberOfSections(in tableView: UITableView) -> Int {
return tableData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableData[section].opened == true{
return tableData[section].sectionData.count + 1
} else{
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
guard let cell = tableView.dequeueReusableCell(withIdentifier: "anotherCell", for: indexPath) as? tableCell else {
fatalError("The dequeued cell has thrown some error.")
}
cell.cellTitle.text = tableData[indexPath.section].title
cell.cellDescription.text = tableData[indexPath.section].description
cell.setData = tableData[indexPath.section].opened
return cell
} else{
guard let cell = tableView.dequeueReusableCell(withIdentifier: "subSectionCell", for: indexPath) as? subSectionTableViewCell else {
fatalError("The dequeued cell has thrown some error.")
}
cell.subSectionTitle.text = tableData[indexPath.section].sectionData[indexPath.row - 1]
return cell
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
if tableData[indexPath.section].opened == true {
tableData[indexPath.section].opened = false
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .none)
}
else{
tableData[indexPath.section].opened = true
let sections = IndexSet.init(integer: indexPath.section)
tableView.reloadSections(sections, with: .none)
}
}
}
Here is the code for hiding and applying the centerYanchor to the cell
var setData: Bool = false {
didSet{
setupCell()
}
}
func setupCell() {
if (cellDescription.text == "") {
cellDescription.isHidden = true
cellTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor, constant: 4).isActive = true
}
if setData{
button.setImage(UIImage(named: "down"), for: .normal)
} else{
button.setImage(UIImage(named: "right"), for: .normal)
}
}
Please suggest me on how I should fix this, and if you have any doubts ask in the comments.
Cell Data Struct
struct cData {
var title = String()
var description = String()
var identifier = Int()
var opened = Bool()
var sectionData = [String]()
}
Cells constraints
Here is my suggested layout.
Yellow is the cell's contentView; orange is the View the contains the other elements; labels have cyan background.
Embed the labels in a UIStackView:
Give the "arrow button" a centerY constraint to the Description label, with Priority: 751 AND give it a centerY constraint to the Title label, with Priority: 750. That will automatically center it on the Description label when it is visible, and on the Title label when Description is hidden.
Then change your cell's setupCell() func as follows:
func setupCell() {
// set all subviews background colors to white
//[contentView, view, cellTitle, cellDescription, button].forEach {
// $0?.backgroundColor = .white
//}
// hide if no text, otherwise show
cellDescription.isHidden = (cellDescription.text == "")
if setData{
button.setImage(UIImage(named: "down"), for: .normal)
} else{
button.setImage(UIImage(named: "right"), for: .normal)
}
}
During dev, I like to use contrasting colors to make it easy to see the layout. If you un-comment the .forEach block, everything will get a white background. After I have my layout correct, I go back to Storyboard and set the background colors to white (or clear, or however I really want them) and remove the color setting from the code.
Looks like a cell reuse issue here:
if (cellDescription.text == "") {
cellDescription.isHidden = true
cellTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor, constant: 4).isActive = true
}
Your cells are being reused, but you're never actually showing the cellDescription if it's available:
if (cellDescription.text == "") {
cellDescription.isHidden = true
cellTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor, constant: 4).isActive = true
} else {
cellDescription.isHidden = false
cellTitle.centerYAnchor.constraint(equalTo: button.centerYAnchor, constant: 4).isActive = false
}

Collection view cells duplicate when scrolling

I've got a collection view and I've got a custom class for the cells. Each cell contains a text view, here is the code:
class CustomWriterPageCell: UICollectionViewCell, UITextViewDelegate {
fileprivate let textViewOne: UITextView = {
let tv = UITextView()
tv.backgroundColor = .cyan
tv.text = "Chapter Title"
tv.font = UIFont(name: "Avenir-Roman", size: 27)
tv.textColor = .gray
return tv
}()
}
Here is the cellForItemAt:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WriterPageCellID", for: indexPath) as! CustomWriterPageCell
cell.backgroundColor = .clear
return cell
}
The text view has a placeholder that I've achieved through the following code (This is inside the custom cell class):
func textViewDidBeginEditing(_ textView: UITextView) {
if textView.textColor == .gray {
textView.text = nil
textView.textColor = .black
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.textColor = .gray
textView.text = "Chapter Title"
}
}
The problem is that, whatever I type on the text view of the first cell, appears on the 4th cell, I know that this is happening because of dequeueReusableCell but I can't seem to solve this problem.
The problem is that you don't specify what text to appear on each of your CollectionViewCell's textView. As long as you don't specify the same in cellForItemAt indexPath it is going to show the reused cell and its content, from dequeueReusableCell as you said.
For the solution to your specific problem you can do as below in the viewcontroller:
`var textViewContentArray = [Int: String]()` //Create a dictionary to hold the texts globally
In textViewDidEndEditing:
func textViewDidEndEditing(_ textView: UITextView) {
if textView.text.isEmpty {
textView.textColor = .gray
textView.text = "Chapter Title"
} else {
guard let cell = textView.superview?.superview as? CustomWriterPageCell else {
return
}
guard let indexPath = self.collectionView.indexPath(for: cell) else { return }
textViewContentArray[indexPath.row] = textView.text
}
}
In textViewDidBeginEditing:
func textViewDidBeginEditing(_ textView: UITextView) {
textView.textColor = .black
}
And in cellForItemAt indexPath:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomWriterPageCell", for: indexPath) as! CustomWriterPageCell
cell.textView.delegate = self
let text = textViewContentArray[indexPath.row]
if !text.isEmpty {
cell.textView.textColor = .black
cell.textView.text = text
} else {
cell.textView.textColor = .gray
cell.textView.text = "Chapter Title"
}
return cell
}
Note: Here I am assuming you have set the Controller which holds the collectionView as delegate for the textViewInCell, if the cell is the delegate you can update the textViewContentArray using protocol
Hope this adds to your understanding.
I am assuming that 4th cell does not fit in current view (i.e available below on scroll - i.e Reused cell)
1) If you are reusing cell then you need to ensure that before cell goes out of view you need to save UITextView data somewhere. Otherwise it will lose data (i.e text written) due to reuse. Save data for each cell in data structure/any form.
2) If you scroll to 4th cell then check if data exists. If does not then set empty state to text view. If you write something then save data for 4th cell in data structure/any form..
(You can use this textViewDidEndEditing() to save current data)
3) Again scroll back to 1st cell type something there are save data of 1st cell data structure/any form for that index.
4) Now scroll back to 4th cell. During reuse set saved data of 4th cell to cell text view.
Data Saved at Index == Cell Index
Make sure you handle your use case correctly. There is nothing wrong with the reuse implementation. Make sure you save data and reset data correctly.
I suggest that the part you want to change is encapsulated into a model, such as the textColor, text you mentioned. Don't manually change the UI display, I mean by modifying the model and then refreshing the UI, so that's not There will be repetitions.
This is the rule for all tables, modify the model to refresh the UI, I hope to be useful to you.

How to style a UITableViewCell different?

I'm trying to set a different style for my active object in a TableView. I tried setting a flag for my object (myObject.isActive) and read it in my custom UITableViewCell like this;
var myArray = [MyObject]()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "myCustomCell", for: indexPath) as? myCustomCell {
if myArray.count > 0 {
// Return the cell
let myObject = myArray[indexPath.row]
cell.updateUI(myObject: myObject)
return cell
}
}
return UITableViewCell()
}
myCustomCell:
func updateUI(myObject: MyObject){
if myObject.isActive {
self.selectedCell()
}
}
func selectedCell() {
labelTitle.font = UIFont(name: "Montserrat-Medium", size: 32)
labelTitle.textColor = UIColor(hex: 0x64BA00)
}
This works great when the tableView data loads. But when I scroll the tableView other cells are also styling differently. How can I solve this?
Cells get reused. You need to handle all possibilities. Your updateUI method changes the cell if myObject is active but you make no attempt to reset the cell if it isn't.
You need something like:
func updateUI(myObject: MyObject){
if myObject.isActive {
selectedCell()
} else {
resetCell()
}
}
And add:
func resetCell() {
// Set the cell's UI as needed
}
Another options is to override the prepareForReuse method of the table cell class. That method should reset the cell to its initial state.

Passing data between TableViewControllers.using didSelect and navigationController without storyboard in Swift 3

I know how to pass data from a UITableViewController to another ViewController. *Both of controllers are UITableViewController.
For example:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as UITableViewCell
let viewcontroller = NextViewController()
viewcontroller.someText = "Any text"
self.navigationController?.pushViewController(viewcontroller, animated: true)
}
What this does is when I select a cell in the TableViewController, in next view controller, for example a UILabel, say myLabel, is set and a string variable is prepared as someText. And set like self.myLabel.text = someText. And when a row is selected, then in the second view controller, "Any text" will be displayed.
However, this is not what I want to use. By which I mean, my goal I am trying to achieve is:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as UITableViewCell
//In this case, the next view controller is UITableView.
let viewcontroller = NextViewController()
// I want a selected cell to lead to contain an array of String,
//so that next view controller will hold multiple cells.
// which are only unique to each cell in first TableViewController.
viewcontroller.someArray = array????
self.navigationController?.pushViewController(viewcontroller, animated: true)
}
Update
In my SecondTableViewCOntroller
There is a nvaigationBar and cells are produced by adding text by a UITextField inside UIAlertAction. These data is saved with UserDefaults.standfard with "table2". Also, there is a variable, an array of String set;
//Gloabally Declared
var myArray = [String]();
var array = [String]();
//This function is displayed in didViewLoad()
func data(){
if let myArray = savedata.stringArray(forKey: KEY) {
array = myArray
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as UITableViewCell
for object in cell.contentView.subviews
{
object.removeFromSuperview();
}
let getData = UserDefaults.standard.stringArray(forKey:"table2")
cell.textLabel?.text = getData[indexPath.row]
cell.textLabel?.font = UIFont(name: familyFont, size: 19)
cell.textLabel?.font = UIFont.systemFont(ofSize: 19)
cell.textLabel?.textColor = UIColor.darkGray
cell.accessoryType = .disclosureIndicator
cell.textLabel?.textAlignment = .center
return cell
}
In my FirstTableViewController:
In this tableViewController, cells are populated by adding text in a UITextField placed on the navigationBar. Then I want the selected cell to hold cells created in SecondTableViewController by adding text by a UITextFieldinsdie UIAlertAction. These data is saved with UserDefaults.standfard with "table1"
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as UITableViewCell
for object in cell.contentView.subviews
{
object.removeFromSuperview();
}
let getData = UserDefaults.standard.stringArray(forKey:"table1")
cell.textLabel?.myArray = getData[indexPath.row]
cell.textLabel?.font = UIFont(name: familyFont, size: 19)
cell.textLabel?.font = UIFont.systemFont(ofSize: 19)
cell.textLabel?.textColor = UIColor.darkGray
cell.accessoryType = .disclosureIndicator
cell.textLabel?.textAlignment = .center
return cell
}
I do not know how to implement code that achieves this. I have researched posts and googled so hard, but what I could find was only about passing data between a view controller and table view controller, but in my case, it is about passing data between TableView to TableView. So this might help others who have the same trouble too.
This has bugged me for a long time. Please help me...
Thanks!
You are close, and the concept stays the same. In this case you would pass the array of Strings that you want to use in the next tableView and use that array as the datasource for the second table so you would have:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = NewTableViewController()
vc.dataSourceArray = array
self.navigationController.pushViewController(vc, animated: true)
}
Then in your new tableView set your numberForSections(), numForRows(), and cellForIndexPath() based on your dataSourceArray. This will populate the new tableView with the passed data.
As per your question, I think you are trying to send your data to custom view class not to viewController class.
So I suggested to customize the init method of view class.
NB : The code I have shown below is written in Swift 2.1.
Here in the example I have taken a UIView and design table view inside that view. I passed the data from the view controller and showing the list of data as dropdown list.
Code :
In the Custom View
init(frame: CGRect, arrData: NSArray) {
super.init(frame: frame)
arrListData = NSMutableArray(array: arrData)
//In this method I have designed the table view
designListView();
}
In the ViewController :
func ListButtonClicked()
{
let arr = ["Asia", "Europe", "Africa", "North America", "South America", "Australia", "Antarctica"]
dropDownlist = DropdownView(frame: CGRectMake(0, 0, UIScreen.mainScreen().bounds.size.width, UIScreen.mainScreen().bounds.size.height), arrData: arr);
dropDownlist.backgroundColor = UIColor.clearColor();
self.view.addSubview(dropDownlist)
}
Here I have mentioned part of code, but for your convenience I am adding the output screen so that you can check whether this fulfil your requirement or not.
My output:

about collection view, the corner cell doesn't keep the backgroundColor even though i change it

I'm making a collection view that has 6 * 7 cells and even though I wrote code such that I could change each cells background colour after declaring the UICollectionView; the cell background colour stays white. Other cells are not returning back to white background colour. Can someone tell me the reason why only this cell doesn't keep it's background colour?
override func viewDidLoad() {
super.viewDidLoad()
let longPressRecognizer = UILongPressGestureRecognizer(target:self, action: #selector(ViewController.longPressAction(_:)))
longPressRecognizer.allowableMovement = 5
longPressRecognizer.minimumPressDuration = 0.5
self.collectionView.addGestureRecognizer(longPressRecognizer)
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let col = indexPath.section
let row = indexPath.row
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as! TimeTableCollectionViewCell
cell.backgroundColor = UIColor.whiteColor()
if !ViewController.colorDictionary.isEmpty {
for (indexPath, color) in ViewController.colorDictionary {
self.collectionView.cellForItemAtIndexPath(indexPath)?.backgroundColor = color
}
}
Please debug the dictionary if condition. It might be going in if statement everytime cell gets return from the collection view.

Resources