Post

Replies

Boosts

Views

Activity

Reply to tableview.reloadRows() takes three hit to reload
@OOPer Finally I am able to solved the issue, I have to wrap the third library methods inside cellForRowAtIndexPath() with DispatchQueue.main.async{} in order for it to work with tableView.reloadRows(). I cannot figure out what is happening here actually haha because with tableView.reloadData() its works well without any problems. Anyway, thank you so much for your suggestions and if you know what is the caused that lead me to wrap those methods with DispatchQueue.main.async{} as stated above please let me know hehe :). I also attached the source code of that third party library in my responses to @Claude31.
Jan ’21
Reply to tableview.reloadRows() takes three hit to reload
@Claude31 Finally I am able to solved this problem by attaching the DispatchQueue.main.async to the strikeLabel() and hideLabel() inside the cellForRowAtIndexPath() method. I can't really understand how things are going on here because using reload data are is working well but while using reloadRows() I have to wrap things to run on the main queue as stated above. Below is the code for the methods I have used, it is a third party library that I use to have a line crossing effect on label's text: // //&#9;StrikethroughLabel.swift //&#9;StrikethroughLabel // //&#9;Created by Charles Prado on 07/04/20. //&#9;Copyright © 2020 Charles Prado. All rights reserved. // import UIKit open class StrikethroughLabel: UILabel { &#9;&#9; &#9;&#9;private var strikeTextLayers = [CAShapeLayer]() &#9;&#9;public func hideStrikeTextLayer() { &#9;&#9;&#9;&#9;self.strikeTextLayers.forEach { layer in layer.removeFromSuperlayer() } &#9;&#9;} &#9;&#9;public func showStrikeTextLayer() { &#9;&#9;&#9;&#9;self.strikeThroughText(duration: 0.0) &#9;&#9;} &#9;&#9;public func strikeThroughText(duration: TimeInterval = 0.3, lineColor: UIColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)) { &#9;&#9;&#9;&#9;self.strikeTextLayers.forEach { layer in layer.removeFromSuperlayer() } &#9;&#9;&#9;&#9;func strikeThroughText(line: Int, inLines lines: [String]) { &#9;&#9;&#9;&#9;&#9;&#9;let baseYPosition = (font.lineHeight * (CGFloat(line - 1) + 0.5)) &#9;&#9;&#9;&#9;&#9;&#9;guard baseYPosition < self.frame.height else { return } &#9;&#9;&#9;&#9;&#9;&#9;let path = UIBezierPath() &#9;&#9;&#9;&#9;&#9;&#9;path.move(to: CGPoint(x: -4, y: baseYPosition)) &#9;&#9;&#9;&#9;&#9;&#9;let attributedText = NSAttributedString(string: lines[line - 1].trimmingCharacters(in: .whitespaces), &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;attributes: [.font: self.font]) &#9;&#9;&#9;&#9;&#9;&#9;let lineMaxX = maxXForLine(withText: attributedText, &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;labelWidth: self.bounds.width) + 4 &#9;&#9;&#9;&#9;&#9;&#9;path.addLine(to: CGPoint(x: lineMaxX, y: baseYPosition)) &#9;&#9;&#9;&#9;&#9;&#9;let shapeLayer = CAShapeLayer() &#9;&#9;&#9;&#9;&#9;&#9;shapeLayer.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor &#9;&#9;&#9;&#9;&#9;&#9;shapeLayer.strokeColor = lineColor.cgColor &#9;&#9;&#9;&#9;&#9;&#9;shapeLayer.lineWidth = 1 &#9;&#9;&#9;&#9;&#9;&#9;shapeLayer.path = path.cgPath &#9;&#9;&#9;&#9;&#9;&#9;self.layer.addSublayer(shapeLayer) &#9;&#9;&#9;&#9;&#9;&#9;let animation = CABasicAnimation(keyPath: "strokeEnd") &#9;&#9;&#9;&#9;&#9;&#9;animation.fromValue = 0 &#9;&#9;&#9;&#9;&#9;&#9;animation.duration = duration &#9;&#9;&#9;&#9;&#9;&#9;shapeLayer.add(animation, forKey: "strikeThroughTextAnimation") &#9;&#9;&#9;&#9;&#9;&#9;self.strikeTextLayers.append(shapeLayer) &#9;&#9;&#9;&#9;} &#9;&#9;&#9;&#9;let lines = self.lines(forLabel: self) ?? [] &#9;&#9;&#9;&#9;let numberOfLines = lines.count &#9;&#9;&#9;&#9;for line in 1...numberOfLines { &#9;&#9;&#9;&#9;&#9;&#9;strikeThroughText(line: line, inLines: lines) &#9;&#9;&#9;&#9;} &#9;&#9;} &#9;&#9;private func lines(forLabel: UILabel) -> [String]? { &#9;&#9;&#9;&#9;guard let text = text, let font = font else { return nil } &#9;&#9;&#9;&#9;let attStr = NSMutableAttributedString(string: text) &#9;&#9;&#9;&#9;attStr.addAttribute(NSAttributedString.Key.font, value: font, range: NSRange(location: 0, length: attStr.length)) &#9;&#9;&#9;&#9;let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString) &#9;&#9;&#9;&#9;let path = CGMutablePath() &#9;&#9;&#9;&#9;let size = sizeThatFits(CGSize(width: self.frame.width, height: .greatestFiniteMagnitude)) &#9;&#9;&#9;&#9;path.addRect(CGRect(x: 0, y: 0, width: size.width, height: size.height), transform: .identity) &#9;&#9;&#9;&#9;let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path, nil) &#9;&#9;&#9;&#9;guard let lines = CTFrameGetLines(frame) as? [Any] else { return nil } &#9;&#9;&#9;&#9;var linesArray: [String] = [] &#9;&#9;&#9;&#9;for line in lines { &#9;&#9;&#9;&#9;&#9;&#9;let lineRef = line as! CTLine &#9;&#9;&#9;&#9;&#9;&#9;let lineRange = CTLineGetStringRange(lineRef) &#9;&#9;&#9;&#9;&#9;&#9;let range = NSRange(location: lineRange.location, length: lineRange.length) &#9;&#9;&#9;&#9;&#9;&#9;let lineString = (text as NSString).substring(with: range) &#9;&#9;&#9;&#9;&#9;&#9;linesArray.append(lineString) &#9;&#9;&#9;&#9;} &#9;&#9;&#9;&#9;return linesArray &#9;&#9;} &#9;&#9;private func maxXForLine(withText text: NSAttributedString, labelWidth: CGFloat) -> CGFloat { &#9;&#9;&#9;&#9;let labelSize = CGSize(width: labelWidth, height: .infinity) &#9;&#9;&#9;&#9;let layoutManager = NSLayoutManager() &#9;&#9;&#9;&#9;let textContainer = NSTextContainer(size: labelSize) &#9;&#9;&#9;&#9;let textStorage = NSTextStorage(attributedString: text) &#9;&#9;&#9;&#9;layoutManager.addTextContainer(textContainer) &#9;&#9;&#9;&#9;textStorage.addLayoutManager(layoutManager) &#9;&#9;&#9;&#9;textContainer.lineFragmentPadding = 0.0 &#9;&#9;&#9;&#9;textContainer.lineBreakMode = .byWordWrapping &#9;&#9;&#9;&#9;textContainer.maximumNumberOfLines = 0 &#9;&#9;&#9;&#9;let lastGlyphIndex = layoutManager.glyphIndexForCharacter(at: text.length - 1) &#9;&#9;&#9;&#9;let lastLineFragmentRect = layoutManager.lineFragmentUsedRect(forGlyphAt: lastGlyphIndex, &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;effectiveRange: nil) &#9;&#9;&#9;&#9;return lastLineFragmentRect.maxX &#9;&#9;} }
Jan ’21
Reply to tableview.reloadRows() takes three hit to reload
@OOPer // //  ViewController.swift //  Todoist // //  Created by David Im on 1/14/21. // import UIKit import SwipeCellKit import StrikethroughLabel class ViewController: UIViewController {     private var todoTableView   = UITableView()     private var searchBar       = UISearchBar()          private var dummyData = [Data(item: "Go to shopping", done: false),                              Data(item: "Go to school", done: false),                              Data(item: "Buy milk", done: false),                              Data(item: "Do homework", done: false),                              Data(item: "Clean room", done: false),                              Data(item: "Wash car", done: false)]          override func viewDidLoad() {         super.viewDidLoad()         view.backgroundColor = .white         title = "Todoist"                  setupViews()     }          private func setupViews() {         view.addSubview(todoTableView)         view.addSubview(searchBar)                  navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addItem))                  searchBar.translatesAutoresizingMaskIntoConstraints                                                         = false         searchBar.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive   = true         searchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive           = true         searchBar.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true                  todoTableView.delegate          = self         todoTableView.dataSource        = self         todoTableView.register(TodoTableViewCell.self, forCellReuseIdentifier: "todoCell")                  //todoTableView.allowsSelection   = true         todoTableView.rowHeight           = 70         //todoTableView.separatorStyle    = .none                      todoTableView.translatesAutoresizingMaskIntoConstraints                                                     = false         todoTableView.topAnchor.constraint(equalTo: searchBar.bottomAnchor, constant: 0).isActive                   = true         todoTableView.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor, constant: 0).isActive     = true         todoTableView.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor, constant: 0).isActive   = true         todoTableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true     }          @objc private func addItem() {                  var dataFromTextField:UITextField?         var addButton:UIAlertAction!                  let addItemBox = UIAlertController(title: "Add Item", message: .none, preferredStyle: .alert)         addItemBox.addTextField { (item) in             dataFromTextField = item         }         addButton = UIAlertAction(title: "Add", style: .cancel) { (action) in             if let data = dataFromTextField?.text, !data.isEmpty {                 self.dummyData.append(Data(item: data, done: false))             }else{                 self.dismiss(animated: true, completion: nil)             }             DispatchQueue.main.async {                 self.todoTableView.reloadData()             }         }         addItemBox.addAction(addButton)         present(addItemBox, animated: true, completion: nil)     } } extension ViewController: UITableViewDataSource, UITableViewDelegate, SwipeTableViewCellDelegate {     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {         return dummyData.count              }          func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {         let todoCell = tableView.dequeueReusableCell(withIdentifier: "todoCell") as! TodoTableViewCell         todoCell.dataLabel.text = dummyData[indexPath.row].item         if (dummyData[indexPath.row].done == false) {             todoCell.dataLabel.hideStrikeTextLayer()         }else{             todoCell.dataLabel.strikeThroughText()         }         todoCell.delegate = self         return todoCell     }          func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {         let index = IndexPath(row: indexPath.row, section: 0)         if dummyData[indexPath.row].done == false {             dummyData[indexPath.row].done = true             todoTableView.reloadRows(at: [index], with: .none)                      }else {             dummyData[indexPath.row].done = false             todoTableView.reloadRows(at: [index], with: .none)         }     }               func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {         guard orientation == .right else { return nil }         let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in             // handle action by updating model with deletion             self.dummyData.remove(at: indexPath.row)             DispatchQueue.main.async {                 self.todoTableView.reloadData()             }         }         // customize the action appearance         deleteAction.image = UIImage(named: "delete")         return [deleteAction]     }          func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {         var options = SwipeOptions()         options.expansionStyle = .destructive         options.transitionStyle = .border         return options     } }
Jan ’21
Reply to tableview.reloadRows() takes three hit to reload
@OOPer Hi, thank you so much for your comment. Actually the code is so long that I cannot paste the rest of it here so I just paste the necessary part here. What I am trying to do is that I try to reload the row of my tableview whenever user click on a specific row by doing all the logic inside the didSelectRowAt() method as I have stated above.
Jan ’21
Reply to tableview.reloadRows() takes three hit to reload
@Claude31 Hi, thank you so much for your suggestions. I have tried to implement the deselectRowAt() and allow multiple selection for my tableview but things still the same. Like I said, implementing just the didSelectRowAt() work perfectly with tableview.reloadData() but when it comes to tableView.reloadRowsAt() it takes a minimum of two clicks to reload the row. My dummyData was set to false before user click on the row, I just don't want to paste everything here because it is too long so I just paste the necessary part only. May you please let me know what do you mean by recompute indexPath? In this part I just use the indexPath from the didSelectRowsAt() method without making any changes to the indexPath. May you let me know what you mean by "There is no need to reset the cell delegate?"
Jan ’21
Reply to Unexpectedly Found nil On TableView While Moving From ParentVC to ContainerVC
@Claude31 Yes, line 24 got crash when I try to switch from my parent viewcontroller. parent ViewController is SearchViewController while the child viewcontroller is SearchAccountViewController. I try to switch to the child ViewController whenever user tap on a UISearchBar. I switch using: class SearchViewController: UIViewController { func showSearchAccountVC() {&#9;&#9;&#9;&#9; print("Begin to move into SearchAccountVC")&#9;&#9;&#9;&#9; let searchAccountVC = SearchAccountViewController()&#9;&#9;&#9;&#9; addChild(searchAccountVC)&#9;&#9;&#9;&#9; self.view.addSubview(searchAccountVC.view)&#9;&#9;&#9;&#9; searchAccountVC.didMove(toParent: self)&#9;&#9;&#9; searchAccountVC.view.frame = self.view.bounds } } This is a delegate method from a UITableViewCell class which will trigger whenever user tap on a UISearchBar. To sum up the problem, tableView is perfectly connected to the ViewController but whenever I try to switch to it from the parent ViewController, Xcode give me the "Unexpected Found nil" message saying that the tableviw is nil.
Dec ’20