首页 > 解决方案 > UITableView 直到 viewDidAppear 才会呈现

问题描述

我正在使用Hero动画过渡到 a UITableViewController,它以某种方式导致我在被调用UITableView之前不渲染任何单元格。viewDidAppear()

我已经确定 UITableView确实在调用之前很久就完成了加载viewDidAppear()(使用日志记录和时间戳),并且已经使用打印语句来确定在调用UITableView时出现的确切时间。viewDidAppear()其他一些堆栈溢出帖子建议放在tableView.reloadData()主线程上,我已经这样做了(无济于事)。

import UIKit
import os.log
import Hero

class MemberTableViewController: UITableViewController, UIGestureRecognizerDelegate {

    // MARK: Properties

    var members: [Member]?

    // drag to exit
    var panGestureRecognizer: UIPanGestureRecognizer!
    var progressBool: Bool = false
    var dismissBool: Bool = false

    // MARK: Overrides

    override func viewDidLoad() {
        super.viewDidLoad()

        // register cells
        tableView.register(UINib(nibName: "MemberTableViewCell", bundle: Bundle.main), forCellReuseIdentifier: "MemberCell")

        // configure hero
        view.hero.isEnabled = true
        view.hero.isEnabledForSubviews = false
        view.hero.id = "members"
        view.hero.modifiers = [.arc(), .useLayerRenderSnapshot]

        // fetch members
        API.shared.getMembers { members in
            self.members = members
            DispatchQueue.main.async {
                self.tableView.reloadSections(IndexSet(arrayLiteral: 0), with: .fade)
            }
        }

        // setup drag to exit
        panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(exit))
        panGestureRecognizer.delegate = self
        view.addGestureRecognizer(panGestureRecognizer)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // customize navigationbar
        self.navigationController?.navigationBar.shadowImage = UIImage()
        self.navigationController?.navigationBar.backgroundColor = self.tableView.backgroundColor
        self.navigationController?.setNavigationBarHidden(false, animated: animated)
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        print("appeared")
    }

    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }

    override var prefersHomeIndicatorAutoHidden: Bool {
        return true
    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return members?.count ?? 0
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "MemberCell", for: indexPath) as? MemberTableViewCell else {
            fatalError("Dequeued cell not an instance of MemberTableViewCell")
        }

        // Configure the cell...
        cell.load(from: members![indexPath.row])

        return cell
    }

    // MARK: Private Methods

    @objc private func exit(recognizer: UIPanGestureRecognizer) {
        let translation = recognizer.translation(in: nil)
        let progressY = (translation.y / 2) / view.bounds.height

        if recognizer.direction == .down && tableView.isAtTop {
            if dismissBool {
                dismissBool = false
                hero.dismissViewController()
                self.hero.modalAnimationType = .uncover(direction: .down)
                progressBool = true
                recognizer.setTranslation(.zero, in: view)
            }
        }

        switch recognizer.state {
        case .changed:
            if progressBool {
                let currentPos = CGPoint(x: view.center.x, y: translation.y + view.center.y)
                Hero.shared.update(progressY)
                Hero.shared.apply(modifiers: [.position(currentPos)], to: view)
            }

        default:
            dismissBool = true
            progressBool = false

            if abs(progressY + recognizer.velocity(in: nil).y / view.bounds.height) > 0.5 {
                Hero.shared.finish()
            } else {
                Hero.shared.cancel()
            }
        }
    }

    // MARK: UIGestureRecognizerDelegate

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }

    // MARK: UIScrollViewDelegate

    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView.isAtTop && scrollView.panGestureRecognizer.direction == .down {
            scrollView.contentOffset = CGPoint(x: 0, y: -(UIApplication.shared.keyWindow?.safeAreaInsets.top ?? 0)
            )
        }
    }
}

我希望UITableView在动画完成的那一刻出现,并且tableView.reloadSections()动画要播放,但似乎动画在UITableView实际出现之前发生,所以它只是在一点延迟后才闪现(看起来很糟糕,见下文)。

发生了什么

注意:禁用 Hero 后,tableView.reloadSections()动画播放没有延迟。

标签: iosswiftuitableview

解决方案


我没有使用您提到的框架,但是您可以尝试添加延迟以重新加载 tableview,直到视图完成渲染:

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    self.tableView.reloadSections(IndexSet(arrayLiteral: 0), with: .fade)
}

三秒钟是很长的时间。我喜欢称其为“穴居人”调试。三秒是一个奇怪的时间,所以如果它有效,请尝试减少延迟因素。

另一个想法是在主队列上也设置成员,所以是这样的:

DispatchQueue.main.async {
    self.members = members
    self.tableView.reloadSections(IndexSet(arrayLiteral: 0), with: .fade)
}

另一个想法是将您的重新加载移动到viewDidAppear().

更新

这是一个断言,您可以使用它来等待视图完全加载并完成调整大小:

var tableViewLoaded: Bool = false

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    guard isViewLoaded, view.window != nil, !tableViewLoaded else { return }
    tableView.reloadSections(IndexSet(arrayLiteral: 0), with: .fade)
    tableViewLoaded = true // flip it to true so you don't constantly reload
}

推荐阅读