从 TableView 打开 ViewController 时有几秒钟的延迟

Posted

技术标签:

【中文标题】从 TableView 打开 ViewController 时有几秒钟的延迟【英文标题】:Lag of several seconds when opening ViewController from TableView 【发布时间】:2019-09-12 00:07:04 【问题描述】:

我正在尝试调试一个非常困难的问题,但我无法深入了解它,所以我想知道是否有人可以分享一些建议。

当调用 didSelectRowAt 时,我有一个 TableView 转换到不同的 VC,当立即注册点击时,后台的某些东西导致新的 VC 仅在 5 秒后出现,我无法弄清楚这是什么原因造成的。

到目前为止我尝试了什么: - 将 iCloud 任务移动到全局线程 - 注释掉整个 iCloud 功能并在本地保存数据 - 禁用 Hero pod 并使用带有或不带有动画的内置 segue - 注释掉 tableview.reloadData() 调用 - 注释掉 viewDidAppear 中的所有内容 - 在 ios12 和 iOS13 GM 上运行它,所以这不是操作系统问题 - 分析应用程序,我看不到任何异常,但我对分析器不是很熟悉

对于冗长的代码转储,我深表歉意,但由于我不确定是什么原因造成的,因此我想提供尽可能多的细节。

非常感谢您分享的任何见解。

主类

import UIKit
import SPAlert
import CoreLocation
import NotificationCenter
import PullToRefreshKit


class List: UIViewController 

    // Outlets
    @IBOutlet weak var plus: UIButton!
    @IBOutlet weak var notes: UIButton!
    @IBOutlet weak var help: UIButton!
    @IBOutlet weak var tableview: UITableView!
    @IBOutlet weak var greeting: UILabel!
    @IBOutlet weak var temperature: UILabel!
    @IBOutlet weak var weatherIcon: UIImageView!
    @IBOutlet weak var weatherButton: UIButton!
    @IBOutlet weak var greetingToTableview: NSLayoutConstraint!

    let locationManager = CLLocationManager()

    @IBAction func notesTU(_ sender: Any) 
        performSegue(withIdentifier: "ToNotes", sender: nil)
    

    @IBAction func notesTD(_ sender: Any) 
        notes.tap(shape: .square)
    

    @IBAction func plusTU(_ sender: Any) 
        hero(destination: "SelectionScreen", type: .zoom)
    

    @IBAction func plusTD(_ sender: Any) 
        plus.tap(shape: .square)
    

    @IBAction func helpTU(_ sender: Any) 
        performSegue(withIdentifier: "ToHelp", sender: nil)        
    

    @IBAction func helpTD(_ sender: Any) 
        help.tap(shape: .square)
    

    @IBAction func weatherButtonTU(_ sender: Any) 
        performSegue(withIdentifier: "OpenModal", sender: nil)
        selectedModal = "Weather"
    


    // Variables
    override var preferredStatusBarStyle: UIStatusBarStyle  return .lightContent 

    // MARK: viewDidLoad
    override func viewDidLoad() 
        super.viewDidLoad()
        tableview.estimatedRowHeight = 200
        tableview.rowHeight = UITableView.automaticDimension

        // Retrieves ideas from the JSON file and assings them to the ideas array
        ideas = readJSONIdeas()
        goals = readJSONGoals()
        ideaStats = readJSONIdeaStats()
        decisions = readJSONDecisions()

        let time = Calendar.current.component(.hour, from: Date())
        switch time 
            case 21...23: greeting.text = "Good Night"
            case 0...4: greeting.text = "Good Night"
            case 5...11: greeting.text = "Good Morning"
            case 12...17: greeting.text = "Good Afternoon"
            case 17...20: greeting.text = "Good Evening"
            default: print("Something went wrong with the time based greeting")
        

        temperature.alpha = 0
        weatherIcon.alpha = 0

        getWeather(temperatureLabel: temperature, iconLabel: weatherIcon)

        NotificationCenter.default.addObserver(self, selector: #selector(self.replayOnboarding), name: Notification.Name(rawValue: "com.cristian-m.replayOnboarding"), object: nil)


        if iCloudIsOn() 
            NotificationCenter.default.addObserver(self, selector: #selector(self.reloadAfteriCloud), name: Notification.Name(rawValue: "com.cristian-m.iCloudDownloadFinished"), object: nil)

            tableview.configRefreshHeader(with: RefreshHeader(),container:self) 
                // After the user pulls to refresh, synciCloud is called and the pull to refresh view is left open.
                // synciCloud posts a notification for key "iCloudDownloadFinished" once it finishes downloading, which then calls reloadAfteriCloud()
                // reloadAfteriCloud() loads the newly downloaded files into memory, reloads the tableview and closes the refresher view
                if iCloudIsAvailable()  synciCloud() 
                else 
                    self.alert(title: "It looks like you're not signed into iCloud on this device",
                          message: "Turn on iCloud in Settings to use iCloud Sync",
                          actionTitles: ["Got it"],
                          actionTypes: [.regular],
                          actions: [nil])
                
            
            synciCloud()
        



        // Responsive Rules
        increasePageInsetsBy(top: 10, left: 20, bottom: 20, right: 20, forDevice: .iPad)
        increasePageInsetsBy(top: 0, left: 0, bottom: 14, right: 0, forDevice: .iPhone8)

        greetingToTableview.resize(to: 80, forDevice: .iPad)

    

    @objc func replayOnboarding(_ notification:NSNotification)
        DispatchQueue.main.asyncAfter(deadline: .now()+0.2) 
            self.hero(destination: "Onboarding1", type: .zoom)
        
    

    @objc func reloadAfteriCloud(_ notification:NSNotification)
        goals = readJSONGoals()
        ideas = readJSONIdeas()
        ideaStats = readJSONIdeaStats()
        decisions = readJSONDecisions()

        tableview.reloadData()
        self.tableview.switchRefreshHeader(to: .normal(.none, 0.0))

        setWeeklyNotification()
    

    @objc func goalCategoryTapped(_ sender: UITapGestureRecognizer?) 
        hero(destination: "GoalStats", type: .pushLeft)
    

    @objc func ideaCategoryTapped(_ sender: UITapGestureRecognizer?) 
        hero(destination: "IdeaStats", type: .pushLeft)
    


    override func viewWillAppear(_ animated: Bool) 
        tableview.reloadData()

        if shouldDisplayGoalCompletedAlert == true 
            shouldDisplayGoalCompletedAlert = false
            SPAlert.present(title: "Goal Completed", preset: .done)
        

        if CLLocationManager.locationServicesEnabled() 
            locationManager.delegate = self
            locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
        

    


tableview 扩展

import UIKit

extension List: UITableViewDelegate, UITableViewDataSource 

    // MARK: numberOfSections
    func numberOfSections(in tableView: UITableView) -> Int  return 3 

    // MARK: viewForHeader
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? 

        let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell") as! CategoryCell

        switch section 
        case 0:
            cell.title.text = "Goals"

            if goals.count != 0  cell.emptyText.text = "You have \(completedGoals.count) achieved goals" 
            else  cell.emptyText.text = "No goals added yet" 

            if activeGoals.count > 0  cell.emptyText.removeFromSuperview() 

            break
        case 1:
            cell.title.text = "Ideas"
            cell.emptyText.text = "No ideas added yet"

            if ideas.count > 0  cell.emptyText.removeFromSuperview() 

            break
        case 2:
            cell.title.text = "Decisions"
            cell.arrow.removeFromSuperview()

            cell.emptyText.text = "No decisions added yet"
            if decisions.count > 0  cell.emptyText.removeFromSuperview() 

            break
        default: print("Something went wrong with the section Switch")
        

        if section == 0 
            cell.button.addTarget(self, action: #selector(goalCategoryTapped(_:)), for: .touchUpInside)
         else if section == 1 
            cell.button.addTarget(self, action: #selector(ideaCategoryTapped(_:)), for: .touchUpInside)
        

        return cell.contentView
    

    // MARK: heightForHeader
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat 

        var cellHeight = CGFloat(60)

        if (activeGoals.count > 0 && section == 0) || (ideas.count > 0 && section == 1) || (decisions.count > 0 && section == 2) 
            cellHeight = CGFloat(40)
        

        return cellHeight
    

    // MARK: numberOfRows
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 

        var numberOfRows: Int = 0

        if section == 0  numberOfRows = activeGoals.count 
        if section == 1  numberOfRows = ideas.count 
        if section == 2  numberOfRows = decisions.count 

        return numberOfRows
    

    // cellForRowAt
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 

        if indexPath.section == 0 
            // Goal Cell
            let cell = tableview.dequeueReusableCell(withIdentifier: "GoalCell", for: indexPath) as! GoalCell

            cell.goalTitle?.text = activeGoals[indexPath.row].title

            if activeGoals[indexPath.row].steps!.count == 1 
                cell.goalNoOfSteps?.text = "\(activeGoals[indexPath.row].steps?.count ?? 0) Step"
             else if activeGoals[indexPath.row].steps!.count > 0 
                cell.goalNoOfSteps?.text = "\(activeGoals[indexPath.row].steps?.count ?? 0) Steps"
             else 
                cell.goalNoOfSteps?.text = "No more steps"
            

            if goals[indexPath.row].stringDate != "I'm not sure yet" 
                cell.goalDuration.text = goals[indexPath.row].timeLeft(from: Date())
             else 
                cell.goalDuration.text = ""
            

            cell.selectionStyle = .none

            cell.background.hero.id = "goal\(realIndexFor(activeGoalAt: indexPath))"

            // Progress Bar
            cell.progressBar.configure(goalsIndex: realIndexFor(activeGoalAt: indexPath))

            return cell

         else if indexPath.section == 1 
            // Idea Cell
            let cell = tableView.dequeueReusableCell(withIdentifier: "IdeaCell", for: indexPath) as! IdeaCell

            cell.ideaTitle.text = ideas[indexPath.row].title
            if cell.ideaDescription != nil 
                cell.ideaDescription.text = String(ideas[indexPath.row].description!.filter  !"\n\t".contains($0) )

                if cell.ideaDescription.text == "Notes" || cell.ideaDescription.text == "" || cell.ideaDescription.text == " " || cell.ideaDescription.text == ideaPlaceholder 
                    cell.ideaDescriptionHeight.constant = 0
                    cell.bottomConstraint.constant = 16
                 else 
                    cell.ideaDescriptionHeight.constant = 38.6
                    cell.bottomConstraint.constant = 22
                
            

            cell.background.hero.id = "idea\(indexPath.row)"

            let image = UIImageView(image: UIImage(named: "delete-accessory"))
            image.contentMode = .scaleAspectFit
            cell.selectionStyle = .none

            return cell
         else 
            // Decision Cell
            let cell = tableView.dequeueReusableCell(withIdentifier: "DecisionCell", for: indexPath) as! DecisionCell
            cell.title.text = decisions[indexPath.row].title

            let image = UIImageView(image: UIImage(named: "delete-accessory"))
            image.contentMode = .scaleAspectFit
            cell.selectionStyle = .none

            return cell
        

    

    // MARK: didSelectRowAt
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        if indexPath.section == 0 
            selectedCell = realIndexFor(activeGoalAt: indexPath)
            performSegue(withIdentifier: "toGoalDetails", sender: nil)
         else if indexPath.section == 1 
            selectedCell = indexPath.row
            performSegue(withIdentifier: "toIdeaDetails", sender: nil)
         else 
            selectedDecision = indexPath.row
            hero(destination: "DecisionDetails", type: .zoom)
        
        print("tap")
    

    // MARK: viewForFooter
    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? 
        let cell = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 10))
        cell.backgroundColor = UIColor(named: "Dark")
        return cell
    

    // MARK: heightForFooter
    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat 
        let height:CGFloat = 18
        return height
    

    // MARK: canEditRowAt
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool  return true 

    // MARK: trailingSwipeActions
    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? 

        let action = UIContextualAction(style: .normal, title: nil, handler:  (action,view,completionHandler ) in

            var message = "This will delete this goal and all its steps permanently"
            if indexPath.section == 1  message = "This will delete this idea permanently" 

            self.alert(title: "Are you sure?",
                       message: message,
                       actionTitles: ["No, cancel", "Yes, delete"],
                       actionTypes: [.regular, .destructive],
                       actions: [ nil,  action1 in
                            tableView.beginUpdates()
                            switch indexPath.section 
                            case 0:
                                deleteGoal(at: realIndexFor(activeGoalAt: indexPath))
                                tableView.deleteRows(at: [indexPath], with: .fade)
                            case 1:
                                deleteIdea(at: indexPath.row)
                                tableView.deleteRows(at: [indexPath], with: .fade)
                            case 2:
                                deleteDecision(at: indexPath.row)
                                tableView.deleteRows(at: [indexPath], with: .fade)
                            default: break
                            
                            tableView.endUpdates()
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: 
                                tableView.reloadData()
                            )
                        ,
                ]
            )
            completionHandler(true)
        )

        action.image = UIImage(named: "delete-accessory")
        action.backgroundColor = UIColor(named: "Dark")
        let confrigation = UISwipeActionsConfiguration(actions: [action])
        confrigation.performsFirstActionWithFullSwipe = false

        return confrigation
    


打开的 VC

import UIKit

class GoalDetails: UIViewController 

    // MARK: Variables
    var descriptionExpanded = false
    var descriptionExists = true
    var keyboardHeight = CGFloat(0)
    override var preferredStatusBarStyle: UIStatusBarStyle  if #available(iOS 13.0, *)  return .darkContent  else  return .default  


    // MARK: Outlets
    @IBOutlet weak var background: UIView!
    @IBOutlet weak var steps: UILabel!
    @IBOutlet weak var detailsTitle: UILabel!
    @IBOutlet weak var detailsDescription: UILabel!
    @IBOutlet weak var tableview: UITableView!
    @IBOutlet weak var progressBar: UIProgressView!
    @IBOutlet weak var plusButton: UIButton!
    @IBOutlet var descriptionHeight: NSLayoutConstraint!
    @IBOutlet weak var completeGoalButton: UIButton!
    @IBOutlet weak var completeGoalButtonHeight: NSLayoutConstraint!
    @IBOutlet weak var progressBarHeight: NSLayoutConstraint!
    @IBOutlet weak var dismissButton: UIButton!
    @IBOutlet weak var editButton: UIButton!
    @IBOutlet weak var tableviewBottomConstraint: NSLayoutConstraint!
    @IBOutlet weak var topToContainer: NSLayoutConstraint!
    @IBOutlet weak var bottomToContainer: NSLayoutConstraint!
    @IBOutlet weak var rightToContainer: NSLayoutConstraint!
    @IBOutlet weak var leftToContainer: NSLayoutConstraint!
    @IBOutlet weak var leftToTableview: NSLayoutConstraint!
    @IBOutlet weak var rightToTableview: NSLayoutConstraint!
    @IBOutlet weak var leftToEdit: NSLayoutConstraint!
    @IBOutlet weak var rightToPlus: NSLayoutConstraint!


    // MARK: Outlet Functions
    @IBAction func completeThisGoal(_ sender: Any) 
        shouldDisplayGoalCompletedAlert = true
        goals[selectedCell].completed = true
        goals[selectedCell].dateAchieved = Date()
        activeGoals = goals.filter  $0.completed == false 
        completedGoals = goals.filter  $0.completed == true 
        writeJSONGoals()
        hero.dismissViewController()

        setWeeklyNotification()
    

    @IBAction func descriptionButtonTU(_ sender: Any) 
        if descriptionExpanded == false 
            descriptionHeight.isActive = false
            descriptionExpanded = true
         else 
            descriptionHeight.isActive = true
            descriptionExpanded = false
        
    

    @IBAction func swipeDown(_ sender: Any) 
        dismissButton.tap(shape: .square)
        hero.dismissViewController()
    


    @IBAction func dismissTU(_ sender: Any) 
        hero.dismissViewController()
    

    @IBAction func dismissTD(_ sender: Any) 
        dismissButton.tap(shape: .square)
    

    @IBAction func plusTU(_ sender: Any) 

        goals[selectedCell].steps?.append(Step(title: ""))

        let numberOfCells = tableview.numberOfRows(inSection: 0)

        tableview.reloadData()
        tableview.layoutIfNeeded()

        DispatchQueue.main.asyncAfter(deadline: .now()+0.2) 
            let cell = self.tableview.cellForRow(at: IndexPath.init(row: numberOfCells, section: 0)) as? StepCell
            cell?.label.becomeFirstResponder()
        

        let indexPath = IndexPath(row: goals[selectedCell].steps!.count - 1, section: 0)
        tableview.scrollToRow(at: indexPath, at: .bottom, animated: true)

        progressBar.configure(goalsIndex: selectedCell)

        configureCompleteGoalButton(buttonHeight: completeGoalButtonHeight, progressBarHeight: progressBarHeight, progressBar: progressBar)

        updateNumberofSteps()
    

    @IBAction func plusTD(_ sender: Any) 
        plusButton.tap(shape: .square)
    


    @IBAction func editTU(_ sender: Any) 
        performSegue(withIdentifier: "ToGoalEdit", sender: nil)
    

    @IBAction func editTD(_ sender: Any) 
        editButton.tap(shape: .rectangle)
    


    // MARK: Class Functions
    func updateNumberofSteps()
        if goals[selectedCell].steps!.count > 0 
            steps.text = "\(goals[selectedCell].steps?.count ?? 0) Steps"
         else 
            steps.text = "No more steps"
        
    


    // MARK: viewDidLoad
    override func viewDidLoad() 

        background.hero.id = "goal\(selectedCell)"

        self.background.clipsToBounds = true
        background.layer.cornerRadius = 16
        background.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]

        updateNumberofSteps()

        // Progress Bar
        progressBar.configure(goalsIndex: selectedCell)

        tableview.emptyDataSetSource = self
        tableview.emptyDataSetDelegate = self

        configureCompleteGoalButton(buttonHeight: completeGoalButtonHeight, progressBarHeight: progressBarHeight, progressBar: progressBar)

        // Responsive Rules
        increasePageInsetsBy(top: 0, left: 0, bottom: 14, right: 0, forDevice: .iPad)
        if UIDevice.current.userInterfaceIdiom == .pad 
            detailsTitle.font = UIFont.boldSystemFont(ofSize: 30)
            topToContainer.constant = 20
            leftToContainer.constant = 40
            rightToContainer.constant = 40
            bottomToContainer.constant = 40

            leftToTableview.constant = 40
            rightToTableview.constant = 40
            leftToEdit.constant = 40
            rightToPlus.constant = 30
        

        increasePageInsetsBy(top: 0, left: 0, bottom: 12, right: 0, forDevice: .iPhone8)
    


    // MARK: viewWillAppear
    override func viewWillAppear(_ animated: Bool) 
        // Deleting a goal from the Edit page seems to also call ViewWillAppear, which causes the app to crash unless checking whether the index exists anymore
        // selectedCell already get assigned the real index of this goal
        if goals.indices.contains(selectedCell) 

            detailsTitle.text = goals[selectedCell].title
            detailsDescription.text = goals[selectedCell].description

            if goals[selectedCell].description == "Reason" || goals[selectedCell].description == "" 
                descriptionHeight.constant = 0
             else 
                descriptionHeight.constant = 58
            
        
    


被调用的 iCloud 相关函数

func iCloudIsAvailable() -> Bool 
    // This function checks whether iCloud is available on the device
    if FileManager.default.ubiquityIdentityToken != nil  return true 
    else  return false 


func iCloudIsOn() -> Bool 
    // This function checks whether the user chose to use iCloud with Thrive
    if UserDefaults.standard.url(forKey: "UDDocumentsPath")! == iCloudPath || UserDefaults.standard.url(forKey: "UDDocumentsPath") == iCloudPath 
        return true
    
    else 
        return false
    


func synciCloud()
    if iCloudIsAvailable() 
            do  try FileManager.default.startDownloadingUbiquitousItem(at: UserDefaults.standard.url(forKey: "UDDocumentsPath")!)
                do 
                    let status = try UserDefaults.standard.url(forKey: "UDDocumentsPath")!.resourceValues(forKeys: [.ubiquitousItemDownloadingStatusKey])

                    while status.ubiquitousItemDownloadingStatus != .current 
                        DispatchQueue.global().asyncAfter(deadline: .now() + 0.5, execute: 
                            print("iCloud still downloading - \(String(describing: status.ubiquitousItemDownloadingStatus))")
                        )
                    
                    DispatchQueue.main.async 
                        NotificationCenter.default.post(name: Notification.Name(rawValue: "com.cristian-m.iCloudDownloadFinished"), object: nil)
                    
                    print("iCloud up to date! - \(String(describing: status.ubiquitousItemDownloadingStatus))")
                
                catch let error  print("Failed to get status: \(error.localizedDescription)") 
            
            catch let error  print("Failed to download iCloud Documnets Folder: \(error.localizedDescription)") 
     else 
        // TODO: Handle use case where iCloud is not available when trying to sync
        print("iCloud is not available on this device")
    


更新:根据 Duncan 的回答,解决问题的方法是将我在 didSelectRowAt 中的三个 Segue 移动到主队列,如下所示:

DispatchQueue.main.async 
    self.performSegue(withIdentifier: "toGoalDetails", sender: nil)

【问题讨论】:

通常在触发 UI 代码和使其生效之间有很长的延迟是从后台线程执行 UIKit 调用的症状。 (这可能会导致各种不良结果,但响应能力的长时间延迟是常见的。)从您的代码中看,我没有看到任何明显的东西,但是您发布了一整串代码,而我没有现在是时候涉足它了。我建议在你进行 UIKit 调用的不同位置设置断点,看看它们是否从线程 1(主线程)以外的任何线程中断。 嗨,Christian,也许您可​​以尝试使用 Whatchdog 检查您的代码。在this answer 中提出了建议,通过它您可以确定问题是否存在于阻塞主线程 5 秒内。 如@DuncanC 所述,这是与线程相关的问题。为什么不在一些并发队列中执行这些操作?想法 = readJSONIdeas() 目标 = readJSONGoals() ideaStats = readJSONIdeaStats() 决策 = readJSONDecisions() @SwissMark,我不知道,但我一定会去看看。谢谢。 @CristianMoisei 您能否标记导致问题的 UIKit 调用,并在您的问题底部添加一个部分,显示您使用的修复程序? (我通常用##EDIT 标记我的问题/答案的更改,以便它们脱颖而出。) 【参考方案1】:

通常如果我无法找出问题的原因,我会执行“二分查找”样式调试。

你提到你已经注释掉了整个 viewDidAppear,但我假设你没有用 viewDidLoad 尝试过。

在这种情况下,我会将viewDidLoad中的所有代码注释掉,运行它,看看延迟是否还在。

如果延迟消失了,我将viewDidLoad中的代码注释掉一半,重新运行。一般来说,一旦我找到导致延迟的一半,我会注释掉一半的“坏”代码并重复,直到找到导致问题的确切行。

【讨论】:

谢谢@Hong Wei,今晚我会试试这个,如果有效,我会回复。你说得对,我还没有为主 vc 注释掉 viewDidLoad。【参考方案2】:

通常在触发 UI 代码和使其生效之间有很长的延迟是从后台线程执行 UIKit 调用的症状。 (这可能会导致各种糟糕的结果,但长时间的响应延迟是常见的。)

我看你的代码并没有看到任何明显的东西,但是你发布了一大堆代码,我现在没有时间涉足它。我建议在你进行 UIKit 调用的不同位置设置断点,看看它们是否从线程 1(主线程)以外的任何线程中断。——Duncan C 昨天删除

【讨论】:

以上是关于从 TableView 打开 ViewController 时有几秒钟的延迟的主要内容,如果未能解决你的问题,请参考以下文章

从相机单击后在 tableView 单元中添加图像

如何从第一个视图切换到 TableView?

如何从其他类重新加载整个 tableview 控制器 [关闭]

删除单元格后刷新tableView

如何修复 UITableView 中的重复搜索结果

Swift:将数据从 tableView 显示到另一个 ViewController(JSON、Alamorife、AlamofireImage)