summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift16
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/AllLocationDataSource.swift21
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/CustomListsDataSource.swift3
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift3
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift195
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationDataSourceProtocol.swift19
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationDiffableDataSourceProtocol.swift91
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift7
-rw-r--r--ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift35
9 files changed, 207 insertions, 183 deletions
diff --git a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift
index d2325e3550..4db1587c6b 100644
--- a/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift
+++ b/ios/MullvadVPN/Coordinators/CustomLists/AddLocationsDataSource.swift
@@ -83,7 +83,7 @@ class AddLocationsDataSource:
indentationLevel: 1
))
}
- updateDataSnapshot(with: [locationsList])
+ reloadDataSnapshot(with: [locationsList])
}
private func isLocationInCustomList(node: LocationNode) -> Bool {
@@ -110,20 +110,12 @@ extension AddLocationsDataSource: UITableViewDelegate {
extension AddLocationsDataSource: LocationCellDelegate {
func toggleExpanding(cell: LocationCell) {
- let items = toggledItems(for: cell).first!.map { item in
- var item = item
- if containsChild(parent: customListLocationNode, child: item.node) {
- item.isSelected = true
- }
- return item
- }
-
- updateDataSnapshot(with: [items], reloadExisting: true, completion: {
+ toggleItems(for: cell) {
if let indexPath = self.tableView.indexPath(for: cell),
let item = self.itemIdentifier(for: indexPath) {
self.scroll(to: item, animated: true)
}
- })
+ }
}
func toggleSelecting(cell: LocationCell) {
@@ -142,7 +134,7 @@ extension AddLocationsDataSource: LocationCellDelegate {
} else {
customListLocationNode.remove(selectedLocation: item.node, with: locationList)
}
- updateDataSnapshot(with: [locationList], completion: {
+ reloadDataSnapshot(with: [locationList], completion: {
let locations = self.customListLocationNode.children.reduce([]) { partialResult, locationNode in
partialResult + locationNode.locations
}
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/AllLocationDataSource.swift b/ios/MullvadVPN/View controllers/SelectLocation/AllLocationDataSource.swift
index bc15ae3976..ac3c9a3b63 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/AllLocationDataSource.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/AllLocationDataSource.swift
@@ -22,6 +22,7 @@ class AllLocationDataSource: LocationDataSourceProtocol {
/// and city names.
func reload(_ relays: LocationRelays) {
let rootNode = RootLocationNode()
+ let expandedRelays = nodes.flatMap { [$0] + $0.flattened }.filter { $0.showsChildren }.map { $0.code }
for relay in relays.relays {
guard case
@@ -32,7 +33,13 @@ class AllLocationDataSource: LocationDataSourceProtocol {
let relayLocation = RelayLocation.hostname(countryCode, cityCode, relay.hostname)
for ancestorOrSelf in relayLocation.ancestors + [relayLocation] {
- addLocation(ancestorOrSelf, rootNode: rootNode, serverLocation: serverLocation, relay: relay)
+ addLocation(
+ ancestorOrSelf,
+ rootNode: rootNode,
+ serverLocation: serverLocation,
+ relay: relay,
+ showsChildren: expandedRelays.contains(ancestorOrSelf.stringRepresentation)
+ )
}
}
@@ -56,7 +63,8 @@ class AllLocationDataSource: LocationDataSourceProtocol {
_ location: RelayLocation,
rootNode: LocationNode,
serverLocation: REST.ServerLocation,
- relay: REST.ServerRelay
+ relay: REST.ServerRelay,
+ showsChildren: Bool
) {
switch location {
case let .country(countryCode):
@@ -64,7 +72,8 @@ class AllLocationDataSource: LocationDataSourceProtocol {
name: serverLocation.country,
code: LocationNode.combineNodeCodes([countryCode]),
locations: [location],
- isActive: true // Defaults to true, updated when children are populated.
+ isActive: true, // Defaults to true, updated when children are populated.
+ showsChildren: showsChildren
)
if !rootNode.children.contains(countryNode) {
@@ -77,7 +86,8 @@ class AllLocationDataSource: LocationDataSourceProtocol {
name: serverLocation.city,
code: LocationNode.combineNodeCodes([countryCode, cityCode]),
locations: [location],
- isActive: true // Defaults to true, updated when children are populated.
+ isActive: true, // Defaults to true, updated when children are populated.
+ showsChildren: showsChildren
)
if let countryNode = rootNode.countryFor(code: countryCode),
@@ -92,7 +102,8 @@ class AllLocationDataSource: LocationDataSourceProtocol {
name: relay.hostname,
code: LocationNode.combineNodeCodes([hostCode]),
locations: [location],
- isActive: relay.active
+ isActive: relay.active,
+ showsChildren: showsChildren
)
if let countryNode = rootNode.countryFor(code: countryCode),
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/CustomListsDataSource.swift b/ios/MullvadVPN/View controllers/SelectLocation/CustomListsDataSource.swift
index 28f40e924a..2176b471bb 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/CustomListsDataSource.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/CustomListsDataSource.swift
@@ -26,9 +26,11 @@ class CustomListsDataSource: LocationDataSourceProtocol {
/// Constructs a collection of node trees by copying each matching counterpart
/// from the complete list of nodes created in ``AllLocationDataSource``.
func reload(allLocationNodes: [LocationNode]) {
+ let expandedRelays = nodes.flatMap { [$0] + $0.flattened }.filter { $0.showsChildren }.map { $0.code }
nodes = repository.fetchAll().map { list in
let customListWrapper = CustomListLocationNodeBuilder(customList: list, allLocations: allLocationNodes)
let listNode = customListWrapper.customListLocationNode
+ listNode.showsChildren = expandedRelays.contains(listNode.code)
listNode.forEachDescendant { node in
// Each item in a section in a diffable data source needs to be unique.
@@ -36,6 +38,7 @@ class CustomListsDataSource: LocationDataSourceProtocol {
// equality, each node code needs to be prefixed with the code of its
// parent custom list to uphold this.
node.code = LocationNode.combineNodeCodes([listNode.code, node.code])
+ node.showsChildren = expandedRelays.contains(node.code)
}
return listNode
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift
index f26a0d2da4..ec7f5b75e2 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationCellViewModel.swift
@@ -34,13 +34,14 @@ extension [LocationCellViewModel] {
mutating func addSubNodes(from item: LocationCellViewModel, at indexPath: IndexPath) {
let section = LocationSection.allCases[indexPath.section]
let row = indexPath.row + 1
+ item.node.showsChildren = true
let locations = item.node.children.map {
LocationCellViewModel(
section: section,
node: $0,
indentationLevel: item.indentationLevel + 1,
- isSelected: false
+ isSelected: item.isSelected
)
}
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift
index 60cd693a3c..c841d1ffda 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSource.swift
@@ -24,6 +24,7 @@ final class LocationDataSource:
private var excludedLocation: LocationCellViewModel?
let tableView: UITableView
let sections: [LocationSection]
+ let serialUpdateQueue = DispatchQueue(label: "LocationDataSource.UpdateQueue")
var didSelectRelayLocations: ((UserSelectedRelays) -> Void)?
var didTapEditCustomLists: (() -> Void)?
@@ -55,103 +56,94 @@ final class LocationDataSource:
}
func setRelays(_ relaysWithLocation: LocationRelays, selectedRelays: RelaySelection) {
- let allLocationsDataSource =
- dataSources.first(where: { $0 is AllLocationDataSource }) as? AllLocationDataSource
-
- let customListsDataSource =
- dataSources.first(where: { $0 is CustomListsDataSource }) as? CustomListsDataSource
-
- allLocationsDataSource?.reload(relaysWithLocation)
- customListsDataSource?.reload(allLocationNodes: allLocationsDataSource?.nodes ?? [])
-
- setSelectedRelays(selectedRelays)
- filterRelays(by: currentSearchString)
+ serialUpdateQueue.async { [weak self] in
+ guard let self = self,
+ let allLocationsDataSource = dataSources
+ .first(where: { $0 is AllLocationDataSource }) as? AllLocationDataSource,
+ let customListsDataSource = dataSources
+ .first(where: { $0 is CustomListsDataSource }) as? CustomListsDataSource else { return }
+ allLocationsDataSource.reload(relaysWithLocation)
+ customListsDataSource.reload(allLocationNodes: allLocationsDataSource.nodes)
+ setSelectedRelays(selectedRelays)
+ filterRelays(by: currentSearchString)
+ }
}
func filterRelays(by searchString: String) {
- currentSearchString = searchString
-
- let list = sections.enumerated().map { index, section in
- dataSources[index]
- .search(by: searchString)
- .flatMap { node in
- let rootNode = RootLocationNode(children: [node])
- return recursivelyCreateCellViewModelTree(for: rootNode, in: section, indentationLevel: 0)
- }
- }
+ serialUpdateQueue.async(flags: .barrier) { [weak self] in
+ guard let self = self else { return }
+ currentSearchString = searchString
- updateDataSnapshot(with: list, reloadExisting: !searchString.isEmpty) {
- self.tableView.reloadData()
+ let list = sections.enumerated().map { index, section in
+ self.dataSources[index]
+ .search(by: searchString)
+ .flatMap { node in
+ let rootNode = RootLocationNode(children: [node])
+ return self.recursivelyCreateCellViewModelTree(
+ for: rootNode,
+ in: section,
+ indentationLevel: 0
+ )
+ }
+ }
- if searchString.isEmpty {
- self.updateSelection(self.selectedLocation, animated: false, completion: {
- self.scrollToSelectedRelay()
- })
- } else {
- self.scrollToTop(animated: false)
+ DispatchQueue.main.async {
+ self.reloadDataSnapshot(with: list) {
+ if searchString.isEmpty, let selectedLocation = self.selectedLocation {
+ self.updateSelection(selectedLocation: selectedLocation, completion: {
+ self.scrollToSelectedRelay()
+ })
+ } else {
+ self.scrollToTop(animated: false)
+ }
+ }
}
}
}
/// Refreshes the custom list section and keeps all modifications intact (selection and expanded states).
- func refreshCustomLists(selectedRelays: RelaySelection) {
- guard let allLocationsDataSource =
- dataSources.first(where: { $0 is AllLocationDataSource }) as? AllLocationDataSource,
- let customListsDataSource =
- dataSources.first(where: { $0 is CustomListsDataSource }) as? CustomListsDataSource
- else {
- return
- }
-
- // Take a "snapshot" of the currently expanded nodes.
- let expandedNodes = customListsDataSource.nodes
- .flatMap { [$0] + $0.flattened }
- .filter { $0.showsChildren }
-
- // Reload data source with (possibly) updated custom lists.
- customListsDataSource.reload(allLocationNodes: allLocationsDataSource.nodes)
-
- // Reapply current selection.
- setSelectedRelays(selectedRelays)
-
- // Reapply current search filter.
- let searchResultNodes = dataSources[0].search(by: currentSearchString)
-
- // Reapply expanded status and override nodes being hidden by search filter.
- RootLocationNode(children: searchResultNodes).forEachDescendant { node in
- node.showsChildren = expandedNodes.contains(node)
- node.isHiddenFromSearch = false
- }
+ func refreshCustomLists() {
+ serialUpdateQueue.async { [weak self] in
+ guard let self = self,
+ let allLocationsDataSource =
+ dataSources.first(where: { $0 is AllLocationDataSource }) as? AllLocationDataSource,
+ let customListsDataSource =
+ dataSources.first(where: { $0 is CustomListsDataSource }) as? CustomListsDataSource
+ else {
+ return
+ }
- // Construct node tree.
- let list = searchResultNodes.flatMap { node in
- let rootNode = RootLocationNode(children: [node])
- return recursivelyCreateCellViewModelTree(for: rootNode, in: .customLists, indentationLevel: 0)
+ // Reload data source with (possibly) updated custom lists.
+ customListsDataSource.reload(allLocationNodes: allLocationsDataSource.nodes)
+ self.filterRelays(by: currentSearchString)
}
-
- updateDataSnapshot(with: [
- list,
- snapshot().itemIdentifiers(inSection: .allLocations),
- ], reloadExisting: true)
}
func setSelectedRelays(_ selectedRelays: RelaySelection) {
- selectedLocation = mapSelection(from: selectedRelays.selected)
-
- excludedLocation = mapSelection(from: selectedRelays.excluded)
- excludedLocation?.excludedRelayTitle = selectedRelays.excludedTitle
-
- tableView.reloadData()
+ serialUpdateQueue.async(flags: .barrier) { [weak self] in
+ guard let self, let _selectedLocation = mapSelection(from: selectedRelays.selected) else { return }
+ selectedLocation = _selectedLocation
+ excludedLocation = mapSelection(from: selectedRelays.excluded)
+ excludedLocation?.excludedRelayTitle = selectedRelays.excludedTitle
+ self.updateSelection(selectedLocation: _selectedLocation, completion: {
+ self.scrollToSelectedRelay()
+ })
+ }
}
- func scrollToSelectedRelay() {
- indexPathForSelectedRelay().flatMap {
- tableView.scrollToRow(at: $0, at: .middle, animated: false)
- }
+ // MARK: - Private functions
+
+ private func scrollToSelectedRelay() {
+ indexPathForSelectedRelay()
+ .flatMap {
+ tableView.scrollToRow(at: $0, at: .middle, animated: false)
+ }
}
private func indexPathForSelectedRelay() -> IndexPath? {
- selectedLocation.flatMap { indexPath(for: $0) }
+ serialUpdateQueue.sync {
+ selectedLocation.flatMap { indexPath(for: $0) }
+ }
}
private func mapSelection(from selectedRelays: UserSelectedRelays?) -> LocationCellViewModel? {
@@ -185,15 +177,16 @@ final class LocationDataSource:
return nil
}
- private func updateSelection(_ item: LocationCellViewModel?, animated: Bool, completion: (() -> Void)? = nil) {
- selectedLocation = item
- guard let selectedLocation else { return }
-
+ private func updateSelection(selectedLocation: LocationCellViewModel, completion: (() -> Void)? = nil) {
let rootNode = selectedLocation.node.root
+ var snapshot = snapshot()
// Exit early if no changes to the node tree should be made.
guard selectedLocation.node != rootNode else {
- completion?()
+ // Apply the updated snapshot
+ DispatchQueue.main.async {
+ self.applySnapshotUsingReloadData(snapshot, completion: completion)
+ }
return
}
@@ -215,22 +208,14 @@ final class LocationDataSource:
indentationLevel: 1
)
- // Insert the new node tree below the selected item.
- var snapshotItems = snapshot().itemIdentifiers(inSection: selectedLocation.section)
- snapshotItems.insert(contentsOf: nodesToAdd, at: indexPath.row + 1)
+ let existingItems = snapshot.itemIdentifiers(inSection: selectedLocation.section)
+ snapshot.deleteItems(nodesToAdd)
+ snapshot.insertItems(nodesToAdd, afterItem: existingItems[indexPath.row])
- let list = sections.enumerated().map { index, section in
- index == indexPath.section
- ? snapshotItems
- : snapshot().itemIdentifiers(inSection: section)
+ // Apply the updated snapshot
+ DispatchQueue.main.async {
+ self.applySnapshotUsingReloadData(snapshot, completion: completion)
}
-
- updateDataSnapshot(
- with: list,
- reloadExisting: true,
- animated: animated,
- completion: completion
- )
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@@ -310,25 +295,21 @@ extension LocationDataSource: UITableViewDelegate {
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
- if let item = itemIdentifier(for: indexPath), item == selectedLocation {
- cell.setSelected(true, animated: false)
+ if let item = itemIdentifier(for: indexPath) {
+ cell.setSelected(item == selectedLocation, animated: false)
}
}
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if let indexPath = indexPathForSelectedRelay() {
- if let cell = tableView.cellForRow(at: indexPath) {
- cell.setSelected(false, animated: false)
- }
+ tableView.deselectRow(at: indexPath, animated: false)
}
-
return indexPath
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let item = itemIdentifier(for: indexPath) else { return }
selectedLocation = item
-
var customListSelection: UserSelectedRelays.CustomListSelection?
if let topmostNode = item.node.root as? CustomListLocationNode {
customListSelection = UserSelectedRelays.CustomListSelection(
@@ -341,7 +322,6 @@ extension LocationDataSource: UITableViewDelegate {
locations: item.node.locations,
customListSelection: customListSelection
)
-
didSelectRelayLocations?(relayLocations)
}
@@ -354,12 +334,9 @@ extension LocationDataSource: LocationCellDelegate {
func toggleExpanding(cell: LocationCell) {
guard let indexPath = tableView.indexPath(for: cell),
let item = itemIdentifier(for: indexPath) else { return }
-
- let items = toggledItems(for: cell)
-
- updateDataSnapshot(with: items, reloadExisting: true, completion: {
+ toggleItems(for: cell) {
self.scroll(to: item, animated: true)
- })
+ }
}
func toggleSelecting(cell: LocationCell) {
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSourceProtocol.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSourceProtocol.swift
index 79f78ebc99..edb1de9196 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSourceProtocol.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationDataSourceProtocol.swift
@@ -18,13 +18,16 @@ protocol LocationDataSourceProtocol {
extension LocationDataSourceProtocol {
func search(by text: String) -> [LocationNode] {
guard !text.isEmpty else {
- resetNodes()
return nodes
}
var filteredNodes: [LocationNode] = []
- searchableNodes.forEach { countryNode in
+ searchableNodes.forEach { node in
+ // Use a copy of the node to preserve the expanded state,
+ // allowing us to restore the previous view state after a search.
+ let countryNode = node.copy()
+
countryNode.showsChildren = false
if countryNode.name.fuzzyMatch(text) {
@@ -62,16 +65,4 @@ extension LocationDataSourceProtocol {
return filteredNodes
}
-
- private func resetNodes() {
- nodes.forEach { node in
- node.showsChildren = false
- node.isHiddenFromSearch = false
-
- node.forEachDescendant { descendantNode in
- descendantNode.showsChildren = false
- descendantNode.isHiddenFromSearch = false
- }
- }
- }
}
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationDiffableDataSourceProtocol.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationDiffableDataSourceProtocol.swift
index 0dce2859e0..81b125a34c 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationDiffableDataSourceProtocol.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationDiffableDataSourceProtocol.swift
@@ -39,51 +39,102 @@ extension LocationDiffableDataSourceProtocol {
}
}
- func toggledItems(for cell: LocationCell) -> [[LocationCellViewModel]] {
+ func toggleItems(for cell: LocationCell, completion: (() -> Void)? = nil) {
guard let indexPath = tableView.indexPath(for: cell),
- let item = itemIdentifier(for: indexPath) else { return [[]] }
-
+ let item = itemIdentifier(for: indexPath) else { return }
+ let snapshot = snapshot()
let section = sections[indexPath.section]
let isExpanded = item.node.showsChildren
- var locationList = snapshot().itemIdentifiers(inSection: section)
+ var locationList = snapshot.itemIdentifiers(inSection: section)
item.node.showsChildren = !isExpanded
if !isExpanded {
locationList.addSubNodes(from: item, at: indexPath)
+ addItem(locationList, toSection: section, index: indexPath.row, completion: completion)
} else {
locationList.removeSubNodes(from: item.node)
+ updateSnapshotRetainingOnly(locationList, toSection: section, completion: completion)
+ }
+ }
+
+ private func addItem(
+ _ items: [LocationCellViewModel],
+ toSection section: LocationSection,
+ index: Int,
+ completion: (() -> Void)? = nil
+ ) {
+ var snapshot = snapshot()
+ let existingItems = snapshot.itemIdentifiers(inSection: section)
+
+ // Filter itemsToAdd to only include items not already in the section
+ let uniqueItems = items.filter { item in
+ existingItems.firstIndex(where: { $0 == item }) == nil
}
- return sections.enumerated().map { index, section in
- index == indexPath.section
- ? locationList
- : snapshot().itemIdentifiers(inSection: section)
+ // Insert unique items at the specified index
+ if index < existingItems.count {
+ snapshot.insertItems(uniqueItems, afterItem: existingItems[index])
+ } else {
+ // If the index is beyond bounds, append to the end
+ snapshot.appendItems(uniqueItems, toSection: section)
}
+ applyAndReconfigureSnapshot(snapshot, in: section, completion: completion)
+ }
+
+ private func updateSnapshotRetainingOnly(
+ _ itemsToKeep: [LocationCellViewModel],
+ toSection section: LocationSection,
+ completion: (() -> Void)? = nil
+ ) {
+ var snapshot = snapshot()
+
+ // Ensure the section exists in the snapshot
+ guard snapshot.sectionIdentifiers.contains(section) else { return }
+
+ // Get the current items in the section
+ let currentItems = snapshot.itemIdentifiers(inSection: section)
+
+ // Determine the items that should be deleted
+ let itemsToDelete = currentItems.filter { !itemsToKeep.contains($0) }
+ snapshot.deleteItems(itemsToDelete)
+
+ // Apply the updated snapshot
+ applyAndReconfigureSnapshot(snapshot, in: section, completion: completion)
}
- func updateDataSnapshot(
+ private func applyAndReconfigureSnapshot(
+ _ snapshot: NSDiffableDataSourceSnapshot<LocationSection, LocationCellViewModel>,
+ in section: LocationSection,
+ completion: (() -> Void)? = nil
+ ) {
+ self.apply(snapshot, animatingDifferences: true) {
+ // After adding, reconfigure specified items to update their content
+ var updatedSnapshot = self.snapshot()
+
+ // Ensure the items exist in the snapshot before attempting to reconfigure
+ let existingItems = updatedSnapshot.itemIdentifiers(inSection: section)
+
+ // Reconfigure the specified items
+ updatedSnapshot.reconfigureItems(existingItems)
+
+ // Apply the reconfigured snapshot without animations to avoid any flickering
+ self.apply(updatedSnapshot, animatingDifferences: false)
+ }
+ }
+
+ func reloadDataSnapshot(
with list: [[LocationCellViewModel]],
- reloadExisting: Bool = false,
animated: Bool = false,
completion: (() -> Void)? = nil
) {
var snapshot = NSDiffableDataSourceSnapshot<LocationSection, LocationCellViewModel>()
-
snapshot.appendSections(sections)
for (index, section) in sections.enumerated() {
let items = list[index]
-
snapshot.appendItems(items, toSection: section)
-
- if reloadExisting {
- snapshot.reconfigureItems(items)
- }
- }
-
- DispatchQueue.main.async {
- self.apply(snapshot, animatingDifferences: animated, completion: completion)
}
+ self.apply(snapshot, animatingDifferences: animated, completion: completion)
}
func recursivelyCreateCellViewModelTree(
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
index 16529c2340..db3cc62b02 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewController.swift
@@ -72,11 +72,6 @@ final class LocationViewController: UIViewController {
}
}
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
- dataSource?.scrollToSelectedRelay()
- }
-
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableView.flashScrollIndicators()
@@ -97,7 +92,7 @@ final class LocationViewController: UIViewController {
}
func refreshCustomLists() {
- dataSource?.refreshCustomLists(selectedRelays: selectedRelays)
+ dataSource?.refreshCustomLists()
}
func setSelectedRelays(_ selectedRelays: RelaySelection) {
diff --git a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift
index 4c0ab086d2..c111b77316 100644
--- a/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift
+++ b/ios/MullvadVPN/View controllers/SelectLocation/LocationViewControllerWrapper.swift
@@ -47,7 +47,7 @@ final class LocationViewControllerWrapper: UIViewController {
private var entryLocationViewController: LocationViewController?
private let exitLocationViewController: LocationViewController
private let segmentedControl = UISegmentedControl()
- private let locationViewContainer = UIStackView()
+ private let locationViewContainer = UIView()
private var selectedEntry: UserSelectedRelays?
private var selectedExit: UserSelectedRelays?
@@ -111,6 +111,8 @@ final class LocationViewControllerWrapper: UIViewController {
setUpNavigation()
setUpSegmentedControl()
addSubviews()
+ add(entryLocationViewController)
+ add(exitLocationViewController)
swapViewController()
}
@@ -226,17 +228,22 @@ final class LocationViewControllerWrapper: UIViewController {
}
}
+ private func add(_ locationViewController: LocationViewController?) {
+ guard let locationViewController else { return }
+ addChild(locationViewController)
+ locationViewController.didMove(toParent: self)
+ locationViewContainer.addConstrainedSubviews([locationViewController.view]) {
+ locationViewController.view.pinEdgesToSuperview()
+ }
+ }
+
@objc
private func segmentedControlDidChange(sender: UISegmentedControl) {
multihopContext = .allCases[segmentedControl.selectedSegmentIndex]
swapViewController()
}
- func swapViewController() {
- locationViewContainer.arrangedSubviews.forEach { view in
- view.removeFromSuperview()
- }
-
+ private func swapViewController() {
var selectedRelays: RelaySelection
var oldViewController: LocationViewController?
var newViewController: LocationViewController?
@@ -263,16 +270,12 @@ final class LocationViewControllerWrapper: UIViewController {
exitLocationViewController
)
}
-
- oldViewController?.removeFromParent()
-
- if let newViewController {
- newViewController.setSelectedRelays(selectedRelays)
-
- addChild(newViewController)
- newViewController.didMove(toParent: self)
-
- locationViewContainer.addArrangedSubview(newViewController.view)
+ newViewController?.setSelectedRelays(selectedRelays)
+ oldViewController?.view.isUserInteractionEnabled = false
+ newViewController?.view.isUserInteractionEnabled = true
+ UIView.animate(withDuration: 0.0) {
+ oldViewController?.view.alpha = 0
+ newViewController?.view.alpha = 1
}
}
}