Search code examples
iosswiftuitableviewuikit

UITableViewController tableview cells showing only while debug


Tableview cells are visible while app is in debug but when tried to terminate app and than try to reopen the app, cells are not visible.

Approach(Which i used in my app):

lazy var contactPickerScene: ContactTabelViewController = {
     guard let contactPickerScene = self.storyboard?.instantiateViewController(identifier: "ContactTabelViewController") as? ContactTabelViewController else {
        return ContactTabelViewController()
       }
      return contactPickerScene
      }()
contactPickerScene.contactDelegate = self
contactPickerScene.multiSelectEnabled = true
contactPickerScene.subtitleCellValue = .email
contactPickerScene.view.frame = self.contactListView.bounds
self.addChild(contactPickerScene)
self.contactListView.addSubview(contactPickerScene.view)
contactPickerScene.didMove(toParent: self)
public class ContactTabelViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {

      ---------------------------
      public override func viewDidLoad() {
        super.viewDidLoad()

        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem
        
        self.title = "Contacts"

        self.tableView.dataSource = self
        self.tableView.delegate = self
        
        inititlizeBarButtons()
        initializeSearchBar()
        registerContactCell()
        fetchContacts()
    }
    ---------------------------
}
// MARK: - Contact Operations
extension ContactTabelViewController {
    func fetchContacts() {
        DispatchQueue.global().async {
            self.getContacts( {(contacts, error) in
                if (error == nil) {
                    DispatchQueue.main.async(execute: {
//                        self.tableView.dataSource = self
//                        self.tableView.delegate = self
                        self.contactsList = contacts
                        self.tableView.reloadData()
                    })
                }
            })
        }
    }
    
    func getContacts(_ completion:  @escaping ContactsHandler) {
        if contactsStore == nil {
            contactsStore = CNContactStore()
        }
        let error = NSError(domain: "ContactPickerErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: "No Contacts Access"])
        
        switch CNContactStore.authorizationStatus(for: CNEntityType.contacts) {
        case CNAuthorizationStatus.denied, CNAuthorizationStatus.restricted:
            DispatchQueue.main.async {
                if self.contactsList.isEmpty {
                    self.tableView.isScrollEnabled = false
                    self.resultSearchController.searchBar.isHidden = true
                    self.tableView.setEmptyMessage("We could not fetch your contacts. Please allow contacts permission.")
                }
            }
        case CNAuthorizationStatus.notDetermined:
            contactsStore?.requestAccess(for: CNEntityType.contacts, completionHandler: { (granted, error) -> Void in
                if  (!granted ){
                    DispatchQueue.main.async(execute: { () -> Void in
                        completion([], error! as NSError?)
                    })
                }
                else{
                    DispatchQueue.global().async {
                        self.orderedContacts = [String: [CNContact]]()
                        self.getContacts(completion)
                    }
                }
            })
        case  CNAuthorizationStatus.authorized:
            var contactsArray = [CNContact]()
            let contactFetchRequest = CNContactFetchRequest(keysToFetch:[CNContactVCardSerialization.descriptorForRequiredKeys()])
            contactFetchRequest.sortOrder = CNContactSortOrder.givenName
            do {
                try contactsStore?.enumerateContacts(with: contactFetchRequest, usingBlock: { (contact, stop) -> Void in
                    var key: String = "#"
                    if let firstLetter = contact.givenName[0..<1] , firstLetter.containsAlphabets() {
                        key = firstLetter.uppercased()
                    }
                    var contacts = [CNContact]()
                    if let segregatedContact = self.orderedContacts[key] {
                        contacts = segregatedContact
                    }
                    if(contactsArray.count == 0){
                        if !Constant.boolIsSubscribe {
                            contacts.append(contact)
                        }
                    }
                    contacts.append(contact)
                    self.orderedContacts[key] = contacts
                    contactsArray.append(contact)
                })
                self.sortedContactKeys = Array(self.orderedContacts.keys).sorted(by: <)
                if self.sortedContactKeys.first == "#" {
                    self.sortedContactKeys.removeFirst()
                    self.sortedContactKeys.append("#")
                }
                completion(contactsArray, nil)
            }
            //Catching exception as enumerateContactsWithFetchRequest can throw errors
            catch let error as NSError {
                print(error.localizedDescription)
            }
        @unknown default:
            break
        }
    }
}

While Debug:

enter image description here

After App Killed:

enter image description here

i want to result same as i got in debug. cell needs to load load while app running after kill from debug state.

in this issue i already spent 2 days and also create separate UItableviewcontroller but getting same result.

XCode Version 15.0 Swift 5.0 iOS 17.0 MacOS Sonoma

i am using this environment currently.

Same issue again in different scenario

i found same issue again in UITableView in same project and history page showing me some history while device attach to system and app is running but whenever i kill app and run it's showing blank tableview.

Logs while connected to device and run

09:16:36: <SharingApp.HistoryViewController: 0x11a4ac770> ==> 146 ==> GetLoadFileFromDocumentDict()
09:16:36: <SharingApp.HistoryViewController: 0x11a4ac770> ==> 2 ==> 171 ==> tableView(_:numberOfRowsInSection:)
09:16:36: <SharingApp.HistoryViewController: 0x11a4ac770> ==> 2 ==> 171 ==> tableView(_:numberOfRowsInSection:)
09:16:36: <SharingApp.HistoryViewController: 0x11a4ac770> ==> 340.0 ==> 262 ==> tableView(_:heightForRowAt:)
09:16:36: <SharingApp.HistoryViewController: 0x11a4ac770> ==> 75.0 ==> 266 ==> tableView(_:heightForRowAt:)
09:16:36: <SharingApp.HistoryViewController: 0x11a4ac770> ==> 2 ==> 171 ==> tableView(_:numberOfRowsInSection:)
09:16:36: <SharingApp.HistoryViewController: 0x11a4ac770> ==> 181 ==> tableView(_:cellForRowAt:)
09:16:36: <SharingApp.HistoryViewController: 0x11a4ac770> ==> 340.0 ==> 262 ==> tableView(_:heightForRowAt:)
09:16:36: <SharingApp.HistoryViewController: 0x11a4ac770> ==> 181 ==> tableView(_:cellForRowAt:)
09:16:36: <SharingApp.HistoryViewController: 0x11a4ac770> ==> 75.0 ==> 266 ==> tableView(_:heightForRowAt:)
09:16:36: <SharingApp.HistoryViewController: 0x11a4ac770> ==> 340.0 ==> 262 ==> tableView(_:heightForRowAt:)
09:16:36: <SharingApp.HistoryViewController: 0x11a4ac770> ==> 75.0 ==> 266 ==> tableView(_:heightForRowAt:)

Logs while not connected to device and run

09:17:49: <SharingApp.HistoryViewController: 0x119563620> ==> 146 ==> GetLoadFileFromDocumentDict()
09:17:49: <SharingApp.HistoryViewController: 0x119563620> ==> 2 ==> 171 ==> tableView(_:numberOfRowsInSection:)
09:17:49: <SharingApp.HistoryViewController: 0x119563620> ==> 2 ==> 171 ==> tableView(_:numberOfRowsInSection:)
09:17:50: <SharingApp.HistoryViewController: 0x119563620> ==> 340.0 ==> 262 ==> tableView(_:heightForRowAt:)
09:17:50: <SharingApp.HistoryViewController: 0x119563620> ==> 75.0 ==> 266 ==> tableView(_:heightForRowAt:)
09:17:50: <SharingApp.HistoryViewController: 0x119563620> ==> 2 ==> 171 ==> tableView(_:numberOfRowsInSection:)
09:17:50: <SharingApp.HistoryViewController: 0x119563620> ==> 340.0 ==> 262 ==> tableView(_:heightForRowAt:)
09:17:50: <SharingApp.HistoryViewController: 0x119563620> ==> 75.0 ==> 266 ==> tableView(_:heightForRowAt:)

i don't know why cellForRowAt is not getting called.

HistoryViewcontroller.Swift

import UIKit
import Photos
import QuickLook
import Kingfisher
import GoogleMobileAds

struct History : Codable {
    let name: String?
    let data: Data?
    let date: Date?
    let size: Int?
    let docType: URLType
}

class HistoryViewController: UIViewController {
    
    @IBOutlet weak var tblHistory: UITableView!
    
    var rightBarButton : UIBarButtonItem!
    var arraySplitArray = [URL]()
    lazy var previewItem = NSURL()
    var arryValue = [URL]()
   
    var imageLocal = [UIImage]()
    var selectedIndexPath = [IndexPath]()
    var boolIsNativeAdHide : Bool = false
    
    enum Errors : Error {
        case error1(str: String?)
        case error2
        case error3
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        rightBarButton = UIBarButtonItem(title: "Delete All", style: .plain, target: self, action: #selector(didTapSelectButton(sender:)))
        navigationItem.rightBarButtonItem = rightBarButton
        
        isSmallNativeAd = false
        tblHistory.register(UINib(nibName: "HistoryTableViewCell2", bundle: nil), forCellReuseIdentifier: "HistoryTableViewCell2")
        tblHistory.register(UINib(nibName: "NativeAdCell", bundle: nil), forCellReuseIdentifier: "NativeAdCell")
        tblHistory.dataSource = self
        tblHistory.delegate = self
        
        self.GetLoadFileFromDocumentDict()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        self.title = "History"
    }

    @objc
    func didTapSelectButton(sender: AnyObject){
        let refreshAlert = UIAlertController(title: "Confirmation", message: "Are you sure you want to delete history?", preferredStyle: UIAlertController.Style.alert)

        refreshAlert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (action: UIAlertAction!) in
            self.CleanHistory()
        }))

        refreshAlert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { (action: UIAlertAction!) in
              print("Handle Cancel Logic here")
        }))
        present(refreshAlert, animated: true, completion: nil)
    }
    
    func CleanHistory(){
        let fileManager = FileManager.default
        let myDocuments = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
        let folderURL = myDocuments.appendingPathComponent(Constant.strFolderName)
        guard let filePaths = try? fileManager.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil, options: []) else { return }
        for filePath in filePaths {
            try? fileManager.removeItem(at: filePath)
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 1){
            // create the alert
            let alert = UIAlertController(title: "Information", message: "History Deleted Successfully.", preferredStyle: UIAlertController.Style.alert)
            
            // add an action (button)
            alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
            
            // show the alert
            self.present(alert, animated: true, completion: nil)
           
        }
        self.GetLoadFileFromDocumentDict()
    }
   
    func GetLoadFileFromDocumentDict() {
        self.arraySplitArray.removeAll()
        self.arryValue.removeAll()
        
        if let arrUrls = FileManager.allRecordedData() {
            arryValue = arrUrls.filter({ url in
                if url.lastPathComponent.contains("png") ||
                    url.lastPathComponent.contains("jpg") ||
                    url.lastPathComponent.contains("gif") ||
                    url.lastPathComponent.contains("tif") ||
                    url.lastPathComponent.contains("JPG") ||
                    url.lastPathComponent.contains("PNG") ||
                    url.lastPathComponent.contains("HEIC") ||
                    url.lastPathComponent.contains("WEBM") ||
                    url.lastPathComponent.contains("MPG") ||
                    url.lastPathComponent.contains("MPEG") ||
                    url.lastPathComponent.contains("MPE") ||
                    url.lastPathComponent.contains("MP4") ||
                    url.lastPathComponent.contains("M4P") ||
                    url.lastPathComponent.contains("M4V") ||
                    url.lastPathComponent.contains("AVI") ||
                    url.lastPathComponent.contains("WMV") ||
                    url.lastPathComponent.contains("MOV") ||
                    url.lastPathComponent.contains("pdf") ||
                    url.lastPathComponent.contains("vcf") ||
                    url.lastPathComponent.contains("m4a") {
                    return true
                }else {
                    return false
                }
            })
            for fileURL in arryValue {
                if(fileURL.pathExtension != ""){
                    arraySplitArray.append(fileURL)
                }
            }
            if arraySplitArray.count > 0 {
                if !Constant.boolIsSubscribe {
                    arraySplitArray.insert(URL(string: "AD")!, at: 0)
                }
                rightBarButton.isEnabled = true
            }else{
                rightBarButton.isEnabled = false
            }
            RBLogger.log("\(self) ==> \(#line) ==> \(#function)")
            self.tblHistory.reloadData()
        }
    }

    func deleteData(at indexPath: IndexPath) {
        print(indexPath.row)
        RemoveFileFromDocumentDictionary(indexPath: indexPath)
    }
    
    func RemoveFileFromDocumentDictionary(indexPath : IndexPath) {
        let fileManager = FileManager.default
        do {
            try fileManager.removeItem(at:arraySplitArray[indexPath.row])
            arraySplitArray.remove(at: indexPath.row)
            self.tblHistory.reloadData()
        } catch {
            print(error)
        }
    }
}

//MARK:- Tableview Methods
extension HistoryViewController : UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        RBLogger.log("\(self) ==> \(arraySplitArray.count) ==> \(#line) ==> \(#function)")
        if arraySplitArray.isEmpty {
            tblHistory.setEmptyView(image: UIImage(named: "noData")!, title: "", message: "")
        }else {
            tblHistory.restoreView()
        }
        return arraySplitArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        RBLogger.log("\(self) ==> \(#line) ==> \(#function)")
        let url = arraySplitArray[indexPath.row]
        if url.absoluteString == "AD" {
            let nativeAdCell = tableView.dequeueReusableCell(withIdentifier: "NativeAdCell") as! NativeAdCell
            if !boolIsNativeAdHide {
                loadNativeAd(view: nativeAdCell.adView, isSmallNativeAd: isSmallNativeAd) {[weak self] isLoad in
                    DispatchQueue.main.async {
                        if !isLoad {
                            self?.boolIsNativeAdHide = true
                            self?.tblHistory.reloadRows(at: [IndexPath(row: indexPath.row, section: indexPath.section)], with: .none)
                        }else {
                            print("Ad already load.")
                        }
                    }
                }
            }
            nativeAdCell.selectionStyle = .none
            return nativeAdCell
        }else {
            guard let cell : HistoryTableViewCell2 = tableView.dequeueReusableCell(withIdentifier: "HistoryTableViewCell2") as? HistoryTableViewCell2 else {
                return UITableViewCell()
            }
            do {
                let checkVideoOrImage = self.checkURLType(inputURL: arraySplitArray[indexPath.row])
                if checkVideoOrImage == "Image" {
                    let data = try Data(contentsOf: self.arraySplitArray[indexPath.row])
                    DispatchQueue.main.async {
                        cell.imgThumb.image = UIImage(data: data)
                    }
                }else if checkVideoOrImage == "Video"{
                    cell.imgThumb.image = self.generateThumbnail(path: arraySplitArray[indexPath.row])
                    
                }else if (arraySplitArray[indexPath.row].pathExtension.lowercased() == "pdf"){
                    cell.imgThumb.image = UIImage.init(named: "pdf")
                }else if (arraySplitArray[indexPath.row].pathExtension.lowercased() == "vcf"){
                    cell.imgThumb.image = UIImage.init(named: "vcf")
                }else if (arraySplitArray[indexPath.row].pathExtension.lowercased() == "m4a"){
                    cell.imgThumb.image = UIImage.init(named: "musical")
                }else {
                    cell.imgThumb.image = UIImage.init(named: "document")
                }
                
                cell.lblName.text = arraySplitArray[indexPath.row].lastPathComponent
                let resourceValues = try arraySplitArray[indexPath.row].resourceValues(forKeys: [.fileSizeKey])
                let fileSize = resourceValues.fileSize!
                cell.lblSize.text = ByteCountFormatter().string(fromByteCount: Int64(fileSize))
            } catch { print(error) }
            
            let attributes = try? FileManager.default.attributesOfItem(atPath: arraySplitArray[indexPath.row].path)
            if let date = attributes?[.modificationDate] {
                print("File Modification date is %@", date)
                let dateFormatter = DateFormatter()
                let tempLocale = dateFormatter.locale
                dateFormatter.locale = Locale(identifier: "en_US_POSIX")
                dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
                dateFormatter.dateFormat = "dd-MM-yyyy"
                dateFormatter.locale = tempLocale
                let dateString = dateFormatter.string(from: date as! Date)
                cell.lblDate.text = dateString
            }
            
            cell.btnViewClick = {(_ aCell: HistoryTableViewCell2) -> Void in
                DispatchQueue.main.async {
                    self.previewItem = self.arraySplitArray[indexPath.row] as NSURL
                    let quickLookController = QLPreviewController()
                    quickLookController.dataSource = self
                    self.present(quickLookController, animated: true, completion: nil)
                }
            }
            cell.selectionStyle = .none
            return cell
        }
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let url = arraySplitArray[indexPath.row]
        if url.absoluteString == "AD" {
            if boolIsNativeAdHide {
                RBLogger.log("\(self) ==> 0.0 ==> \(#line) ==> \(#function)")
                return 0.0
            }else{
                RBLogger.log("\(self) ==> \(nativeAdHeight() + 40.0) ==> \(#line) ==> \(#function)")
                return nativeAdHeight() + 40.0
            }
        }else {
            RBLogger.log("\(self) ==> 75.0 ==> \(#line) ==> \(#function)")
            return 75.0
        }
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let url = arraySplitArray[indexPath.row]
        if url.absoluteString == "AD" {
            return
        }
        if(tblHistory.isEditing){
            selectedIndexPath.append(indexPath)
            print(indexPath.row)
        }
    }
    
    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        let url = arraySplitArray[indexPath.row]
        if url.absoluteString == "AD" {
            return
        }
        if(tblHistory.isEditing){
            selectedIndexPath.remove(at: indexPath.row)
            print(indexPath.row)
        }
    }
    
    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let url = arraySplitArray[indexPath.row]
        if url.absoluteString == "AD" {
            return nil
        }
        let deleteAction = UIContextualAction(style: .destructive, title: "Delete") {  (contextualAction, view, boolValue) in
            self.deleteData(at: indexPath)
        }
        let swipeActions = UISwipeActionsConfiguration(actions: [deleteAction])
        
        return swipeActions
    }
    
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
}

extension HistoryViewController : QLPreviewControllerDataSource {
    func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
        return 1
    }
    
    func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
        return previewItem as QLPreviewItem
    }
}

extension HistoryViewController {
    func checkURLType(inputURL : URL) -> String {
        
        // Most commom image types..
        let imageExtensions = ["png", "jpg", "gif", "tif", "JPG", "PNG","HEIC"]
        
        // Most cmmon video types..
        let videoExtensions = ["WEBM", "MPG", "MPEG", "MPE", "MP4", "M4P", "M4V", "AVI", "WMV", "MOV"]
        
        let url: URL? = URL(fileURLWithPath: inputURL.path)
        let pathExtention = url?.pathExtension
        
        if imageExtensions.contains(pathExtention!) {
            print("Image URL History: \(String(describing: url))")
            return "Image"
        }else if videoExtensions.contains(pathExtention!) {
            print("Video URL: \(String(describing: url))")
            return "Video"
        }else {
            print("Does Not Exist: \(String(describing: url))")
            return ""
        }
    }
    
    func generateThumbnail(path: URL) -> UIImage? {
        do {
            let asset = AVURLAsset(url: path, options: nil)
            let imgGenerator = AVAssetImageGenerator(asset: asset)
            imgGenerator.appliesPreferredTrackTransform = true
            let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil)
            let thumbnail = UIImage(cgImage: cgImage)
            return thumbnail
        } catch let error {
            print("*** Error generating thumbnail: \(error.localizedDescription)")
            return nil
        }
    }
}

Solution

  • This 2 solution work for me

    First:

    i don't know why but when i tried to comment this method heightForRowAt than cellForRowAt method called.

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            let url = arraySplitArray[indexPath.row]
            if url.absoluteString == "AD" {
                if boolIsNativeAdHide {
                    return 0.0
                }else {
                    return nativeAdHeight() + 40.0
                }
            }else {
                return 75.0
            }
        }
    

    Second:

    Whenever i tried to set heightForRowAt value as UITableView.automaticDimension

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return UITableView.automaticDimension
    }