From 85e1e131f6dbe146e57bec19b1042cfbc69c0c1f Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Thu, 12 Aug 2021 19:36:28 -0400 Subject: [PATCH] Fix crash when fetching recommended instances fails --- .../InstanceSelectorTableViewController.swift | 93 ++++++++++++++----- 1 file changed, 72 insertions(+), 21 deletions(-) diff --git a/Tusker/Screens/Onboarding/InstanceSelectorTableViewController.swift b/Tusker/Screens/Onboarding/InstanceSelectorTableViewController.swift index a39dc661..fecc7125 100644 --- a/Tusker/Screens/Onboarding/InstanceSelectorTableViewController.swift +++ b/Tusker/Screens/Onboarding/InstanceSelectorTableViewController.swift @@ -49,6 +49,11 @@ class InstanceSelectorTableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() + + // disable transparent background when scrolled to top because it gets weird with animating table items in and out + let appearance = UINavigationBarAppearance() + appearance.configureWithDefaultBackground() + navigationItem.scrollEdgeAppearance = appearance tableView.register(UINib(nibName: "InstanceTableViewCell", bundle: .main), forCellReuseIdentifier: instanceCell) @@ -120,14 +125,18 @@ class InstanceSelectorTableViewController: UITableViewController { client.run(request) { (response) in var snapshot = self.dataSource.snapshot() if snapshot.indexOfSection(.selected) != nil { - snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .selected)) + snapshot.deleteSections([.selected]) } if case let .success(instance, _) = response { - if !snapshot.sectionIdentifiers.contains(.selected) { + if snapshot.indexOfSection(.recommendedInstances) != nil { + snapshot.insertSections([.selected], beforeSection: .recommendedInstances) + } else { snapshot.appendSections([.selected]) } + snapshot.appendItems([.selected(url, instance)], toSection: .selected) + DispatchQueue.main.async { self.dataSource.apply(snapshot) } @@ -137,14 +146,47 @@ class InstanceSelectorTableViewController: UITableViewController { private func loadRecommendedInstances() { InstanceSelector.getInstances(category: nil) { (response) in - guard case let .success(instances, _) = response else { fatalError() } - - self.recommendedInstances = instances - self.filterRecommendedResults() + DispatchQueue.main.async { + switch response { + case let .failure(error): + self.showRecommendationsError(error) + case let .success(instances, _): + self.recommendedInstances = instances + self.filterRecommendedResults() + } + } } } - func filterRecommendedResults() { + private func showRecommendationsError(_ error: Client.Error) { + let footer = UITableViewHeaderFooterView() + footer.translatesAutoresizingMaskIntoConstraints = false + + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.textColor = .secondaryLabel + label.textAlignment = .center + label.font = .boldSystemFont(ofSize: 17) + label.numberOfLines = 0 + label.text = "Could not fetch suggested instances: \(error.localizedDescription)" + + footer.contentView.addSubview(label) + + NSLayoutConstraint.activate([ + label.leadingAnchor.constraint(equalToSystemSpacingAfter: footer.contentView.leadingAnchor, multiplier: 1), + footer.contentView.trailingAnchor.constraint(equalToSystemSpacingAfter: label.trailingAnchor, multiplier: 1), + label.topAnchor.constraint(equalTo: footer.contentView.topAnchor, constant: 8), + label.bottomAnchor.constraint(equalTo: footer.contentView.bottomAnchor, constant: 8), + ]) + + let fittingSize = CGSize(width: tableView.bounds.width - (tableView.safeAreaInsets.left + tableView.safeAreaInsets.right), height: 0) + let size = footer.systemLayoutSizeFitting(fittingSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) + footer.frame = CGRect(origin: .zero, size: size) + + tableView.tableFooterView = footer + } + + private func filterRecommendedResults() { let filteredInstances: [InstanceSelector.Instance] if let currentQuery = currentQuery, !currentQuery.isEmpty { filteredInstances = recommendedInstances.filter { @@ -155,12 +197,20 @@ class InstanceSelectorTableViewController: UITableViewController { } var snapshot = self.dataSource.snapshot() - snapshot.deleteSections([.recommendedInstances]) - snapshot.appendSections([.recommendedInstances]) - snapshot.appendItems(filteredInstances.map { Item.recommended($0) }, toSection: .recommendedInstances) - DispatchQueue.main.async { - self.dataSource.apply(snapshot) + if snapshot.indexOfSection(.recommendedInstances) != nil { + let toRemove = snapshot.itemIdentifiers(inSection: .recommendedInstances).filter { + if case .recommended(_) = $0 { + return true + } else { + return false + } + } + snapshot.deleteItems(toRemove) + } else { + snapshot.appendSections([.recommendedInstances]) } + snapshot.appendItems(filteredInstances.map { Item.recommended($0) }, toSection: .recommendedInstances) + self.dataSource.apply(snapshot) } // MARK: - Table view delegate @@ -194,29 +244,30 @@ extension InstanceSelectorTableViewController { case recommended(InstanceSelector.Instance) static func ==(lhs: Item, rhs: Item) -> Bool { - if case let .selected(url, instance) = lhs, - case let .selected(otherUrl, other) = rhs { - return url == otherUrl && instance.uri == other.uri - } else if case let .recommended(instance) = lhs, - case let .recommended(other) = rhs { - return instance.domain == other.domain + switch (lhs, rhs) { + case let (.selected(urlA, instanceA), .selected(urlB, instanceB)): + return urlA == urlB && instanceA.uri == instanceB.uri + case let (.recommended(a), .recommended(b)): + return a.domain == b.domain + default: + return false } - return false } func hash(into hasher: inout Hasher) { switch self { case let .selected(url, instance): - hasher.combine(Section.selected) + hasher.combine(0) hasher.combine(url) hasher.combine(instance.uri) case let .recommended(instance): - hasher.combine(Section.recommendedInstances) + hasher.combine(1) hasher.combine(instance.domain) } } } class DataSource: UITableViewDiffableDataSource { + } }