Skip to content

Commit

Permalink
Fixing UI issues in iOS 18 (#674)
Browse files Browse the repository at this point in the history
* Fixed duplicated navigation in iOS 18

* Link apple forum discussion

* Fixed issue when some UI components where not clickable
  • Loading branch information
Oleg-Pecheneg authored Sep 19, 2024
1 parent d7f67cf commit 4396ac3
Show file tree
Hide file tree
Showing 11 changed files with 85 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ private extension HomeActivityTransactionsSectionView {
viewModel.didSelectTx(tx: tx)
} label: {
WalletTransactionDisplayInfoListItemView(transaction: tx)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ final class HomeTabRouter: ObservableObject {

@Published var tabViewSelection: HomeTab = .wallets
@Published var pullUp: ViewPullUpConfigurationType?
@Published var walletViewNavPath: [HomeWalletNavigationDestination] = []
@Published var chatTabNavPath: [HomeChatNavigationDestination] = []
@Published var exploreTabNavPath: [HomeExploreNavigationDestination] = []
@Published var activityTabNavPath: [HomeActivityNavigationDestination] = []
@Published var walletViewNavPath: NavigationPathWrapper<HomeWalletNavigationDestination> = .init()
@Published var chatTabNavPath: NavigationPathWrapper<HomeChatNavigationDestination> = .init()
@Published var exploreTabNavPath: NavigationPathWrapper<HomeExploreNavigationDestination> = .init()
@Published var activityTabNavPath: NavigationPathWrapper<HomeActivityNavigationDestination> = .init()
@Published var presentedNFT: NFTDisplayInfo?

@Published var presentedDomain: DomainPresentationDetails?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,28 @@

import SwiftUI

typealias EmptyNavigationPath = Array<Int>
typealias EmptyNavigationPath = NavigationPathWrapper<Int>

struct NavigationViewWithCustomTitle<Content: View, Data>: View where Data : MutableCollection, Data : RandomAccessCollection, Data : RangeReplaceableCollection, Data.Element : Hashable {
struct NavigationViewWithCustomTitle<Content: View, Data>: View where Data: Hashable {

@Environment(\.dismiss) var dismiss

@ViewBuilder var content: () -> Content
var navigationStateProvider: (NavigationStateManager)->()
@Binding var path: Data
@Binding var path: NavigationPathWrapper<Data>
private var navPath: Binding<NavigationPath> {
Binding {
path.navigationPath
} set: { navPath in
path.navigationPath = navPath
}

}
@StateObject private var navigationState = NavigationStateManager()
@State private var viewPresentationStyle: ViewPresentationStyle = .fullScreen

var body: some View {
NavigationStack(path: $path) {
NavigationStack(path: navPath) {
content()
.navigationPopGestureDisabled(navigationState.navigationBackDisabled)
.environmentObject(navigationState)
Expand Down Expand Up @@ -96,3 +104,57 @@ final class NavigationStateManagerWrapper: ObservableObject {
@Published var navigationState: NavigationStateManager?

}


/// Navigation Path wrapper. Since iOS 18.0, If NavigationStack is used with array (like before) it now pushes two view controllers each time element appended to that error.
/// Issue is not reproducible if NavigationPath is used. This wrapper allows to avoid this bug while preserving existing functionality.
/// Navigation path should listen for didSet because user can swipe back manually and we need to adjust underlying array of typed elements.
/// Issue discussion on Apple Dev forum: https://forums.developer.apple.com/forums/thread/759542
struct NavigationPathWrapper<Data> where Data : Hashable {
var navigationPath: NavigationPath = NavigationPath() {
didSet {
if navigationPath.count < navigationTypedPath.count {
navigationTypedPath = Array(navigationTypedPath.prefix(navigationPath.count))
} else if navigationPath.count > navigationTypedPath.count {
Debugger.printFailure("Should never use NavigationLink. Only NavigationPathWrapper.", critical: true)
}
}
}
private var navigationTypedPath: [Data] = []

var last: Data? { navigationTypedPath.last }
var isEmpty: Bool { navigationPath.isEmpty }
var count: Int { navigationPath.count }
var indices: Range<Int> { navigationTypedPath.indices }

mutating func append(_ item: Data) {
navigationTypedPath.append(item)
navigationPath.append(item)
}

mutating func removeAll() {
navigationPath = NavigationPath()
}

mutating func removeLast() {
navigationPath.removeLast()
}

func first(where isIncluded: (Data) -> Bool) -> Data? {
navigationTypedPath.first(where: isIncluded)
}

subscript(index: Int) -> Data {
navigationTypedPath[index]
}
}

extension NavigationPathWrapper: Hashable {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.navigationTypedPath == rhs.navigationTypedPath
}

func hash(into hasher: inout Hasher) {
hasher.combine(navigationTypedPath)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class SendCryptoAssetViewModel: ObservableObject {

@Published var sourceWallet: WalletEntity
@Published var navigationState: NavigationStateManager?
@Published var navPath: [SendCryptoAsset.NavigationDestination] = []
@Published var navPath: NavigationPathWrapper<SendCryptoAsset.NavigationDestination> = .init()
@Published var isLoading = false
@Published var error: Error?
private let cryptoSender: UniversalCryptoSenderProtocol?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,20 @@ import SwiftUI

struct HomeSettingsNavButtonView: View, ViewAnalyticsLogger {

@EnvironmentObject var tabRouter: HomeTabRouter
@Environment(\.analyticsViewName) var analyticsName
@Environment(\.analyticsAdditionalProperties) var additionalAppearAnalyticParameters

var body: some View {
NavigationLink(value: HomeWalletNavigationDestination.settings(.none)) {
Button {
logButtonPressedAnalyticEvents(button: .settings)
tabRouter.walletViewNavPath.append(HomeWalletNavigationDestination.settings(.none))
} label: {
Image.settingsIcon
.resizable()
.squareFrame(24)
.foregroundStyle(Color.foregroundDefault)
}
.onButtonTap {
logButtonPressedAnalyticEvents(button: .settings)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ private extension HomeWalletNFTsCollectionSectionView {
.nftName : nft.displayName])
} label: {
HomeWalletNFTCellView(nft: nft)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ private extension HomeWalletsDomainsSectionView {
domainSelectedCallback(domain)
} label: {
HomeWalletDomainCellView(domain: domain)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class ActivateMPCWalletViewModel: ObservableObject {

let preFilledEmail: String?
let activationResultCallback: ActivateMPCWalletFlow.FlowResultCallback
@Published var navPath: [ActivateMPCWalletFlow.NavigationDestination] = []
@Published var navPath: NavigationPathWrapper<ActivateMPCWalletFlow.NavigationDestination> = .init()
@Published var navigationState: NavigationStateManager?
@Published var isLoading = false
@Published var error: Error?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class ReconnectMPCWalletViewModel: ObservableObject {

private let reconnectData: MPCWalletReconnectData
let reconnectResultCallback: ReconnectMPCWalletFlow.FlowResultCallback
@Published var navPath: [ReconnectMPCWalletFlow.NavigationDestination] = []
@Published var navPath: NavigationPathWrapper<ReconnectMPCWalletFlow.NavigationDestination> = .init()
@Published var navigationState: NavigationStateManager?
@Published var isLoading = false
@Published var error: Error?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final class PurchaseMPCWalletViewModel: ObservableObject {

let createWalletCallback: AddWalletResultCallback

@Published var navPath: [PurchaseMPCWallet.NavigationDestination] = []
@Published var navPath: NavigationPathWrapper<PurchaseMPCWallet.NavigationDestination> = .init()
@Published var navigationState: NavigationStateManager?
private var mpcTakeoverCredentials: MPCTakeoverCredentials?
@Published var isLoading = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ private extension SettingsProfilesView {
tabRouter.walletViewNavPath.append(.walletDetails(wallet))
} label: {
SettingsProfileTileView(profile: profile)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
}
Expand All @@ -65,6 +66,7 @@ private extension SettingsProfilesView {
}
} label: {
SettingsProfileTileView(profile: profile)
.contentShape(Rectangle())
}
.onButtonTap {
logButtonPressedAnalyticEvents(button: .logOut)
Expand Down

0 comments on commit 4396ac3

Please sign in to comment.