简体   繁体   中英

UITableView doesn't render until viewDidAppear

I am using Hero for animating a transition to a UITableViewController , and it is somehow causing my UITableView to not render any cells until after viewDidAppear() gets called.

I've determined that the UITableView does finish loading long before viewDidAppear() is called (using logging and timestamps), and have used print statements to determine that the UITableView appears exactly when viewDidAppear() is called. Some other stack overflow posts suggested putting tableView.reloadData() on the main thread, which I've done (to no avail).

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)
            )
        }
    }
}

I would expect the UITableView to show up the instant the animation finishes, and the tableView.reloadSections() animation to play, but it seems the animation happens before the UITableView actually appears, so it just flashes into view after a little delay (which looks awful, see below).

发生了什么

NOTE : With Hero disabled, the tableView.reloadSections() animation plays with no delay.

I haven't worked with the framework you mentioned, but you could try adding a delay to reloading the tableview until the view's finished rendering:

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

Three seconds is a long time. I like to call this "caveman" debugging. Three seconds is an outlandish amount of time, so if it works, try cutting back the delay factor.

Another thought would be to set members on the main queue, as well, so something like this:

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

Another thought would be to move your reload to viewDidAppear() .

Update

Here's an assertion you could use to wait until the view is fully loaded and done resizing:

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM