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()
.
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.