diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 674b7ab75c..c70aac96c6 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -46,6 +46,13 @@ struct ChatListView: View { } } + var isUserPickerSheetPresented: Binding { + Binding( + get: { activeUserPickerSheet != nil }, + set: { if !$0 { activeUserPickerSheet = nil } } + ) + } + private var viewBody: some View { ZStack(alignment: oneHandUI ? .bottomLeading : .topLeading) { NavStackCompat( @@ -58,8 +65,9 @@ struct ChatListView: View { destination: chatView ) { chatListView } } - .sheet(item: $activeUserPickerSheet) { sheet in - if let currentUser = chatModel.currentUser { + .sheetWithDetents(isPresented: isUserPickerSheetPresented) { + if let sheet = activeUserPickerSheet, let currentUser = chatModel.currentUser { + Group { switch sheet { case .address: NavigationView { @@ -95,10 +103,14 @@ struct ChatListView: View { activeSheet: $activeUserPickerSheet ) } + } + .environmentObject(ChatModel.shared) + .environmentObject(AppTheme.shared) } } } + private var chatListView: some View { let tm = ToolbarMaterial.material(toolbarMaterial) return withToolbar(tm) { diff --git a/apps/ios/Shared/Views/Helpers/SheetWithDetents.swift b/apps/ios/Shared/Views/Helpers/SheetWithDetents.swift new file mode 100644 index 0000000000..c2ba4e7365 --- /dev/null +++ b/apps/ios/Shared/Views/Helpers/SheetWithDetents.swift @@ -0,0 +1,80 @@ +import SwiftUI + +extension View { + @ViewBuilder + func sheetWithDetents( + isPresented: Binding, + detents: [UISheetPresentationController.Detent] = [.medium(), .large()], + @ViewBuilder content: @escaping () -> Content + ) -> some View { + if #available(iOS 16, *) { + sheet(isPresented: isPresented, content: content) + .presentationDetents( + Set(detents.compactMap { presentationDetent(detent:$0) }) + ) + } else { + ZStack { + SheetPresenter( + isPresented: isPresented, + detents: detents, + content: content() + ) + self + } + } + } +} + +@available(iOS 16.0, *) +private func presentationDetent(detent: UISheetPresentationController.Detent) -> PresentationDetent? { + let map: [UISheetPresentationController.Detent.Identifier: PresentationDetent] = [ + .medium: .medium, + .large: .large + ] + return map[detent.identifier] +} + +/// An transparent view which is added to SwfttUI view hierarchy an presents a UIKit sheet with detents +private struct SheetPresenter: UIViewRepresentable { + @Binding var isPresented: Bool + let detents: [UISheetPresentationController.Detent] + let content: Content + + func makeUIView(context: Context) -> UIView { UIView() } + + func updateUIView(_ uiView: UIView, context: Context) { + let hc = UIHostingController(rootView: content) + if let spc = hc.presentationController as? UISheetPresentationController { + spc.detents = detents + spc.prefersGrabberVisible = true + spc.prefersScrollingExpandsWhenScrolledToEdge = false + spc.largestUndimmedDetentIdentifier = .medium + } + hc.presentationController?.delegate = context.coordinator + if let root = uiView.window?.rootViewController { + if isPresented { + if root.presentedViewController != nil { + // Simultaneous sheets are not allowed - dismiss the previous one + root.dismiss(animated: true) { root.present(hc, animated: true) } + } else { + root.present(hc, animated: true) + } + } else { + root.dismiss(animated: true) + } + } + } + + func makeCoordinator() -> Coordinator { Coordinator(isPresented: $isPresented) } + + class Coordinator: NSObject, UISheetPresentationControllerDelegate { + @Binding var isPresented: Bool + + init(isPresented: Binding) { self._isPresented = isPresented } + + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + isPresented = false + } + } +} + diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 3700fbbf16..d6f9a511ab 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -201,6 +201,7 @@ CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; }; CE75480A2C622630009579B7 /* SwipeLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7548092C622630009579B7 /* SwipeLabel.swift */; }; CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */; }; + CEB7E06F2C8779C800BC270C /* SheetWithDetents.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEB7E06E2C8779C800BC270C /* SheetWithDetents.swift */; }; CEDE70222C48FD9500233B1F /* SEChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEDE70212C48FD9500233B1F /* SEChatState.swift */; }; CEE723AA2C3BD3D70009AE93 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */; }; CEE723B12C3BD3D70009AE93 /* SimpleX SE.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -537,6 +538,7 @@ CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = ""; }; CE7548092C622630009579B7 /* SwipeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeLabel.swift; sourceTree = ""; }; CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemClipShape.swift; sourceTree = ""; }; + CEB7E06E2C8779C800BC270C /* SheetWithDetents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetWithDetents.swift; sourceTree = ""; }; CEDE70212C48FD9500233B1F /* SEChatState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEChatState.swift; sourceTree = ""; }; CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX SE.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; CEE723A92C3BD3D70009AE93 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; @@ -789,6 +791,7 @@ 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */, CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */, CE7548092C622630009579B7 /* SwipeLabel.swift */, + CEB7E06E2C8779C800BC270C /* SheetWithDetents.swift */, ); path = Helpers; sourceTree = ""; @@ -1431,6 +1434,7 @@ 647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */, 646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */, 8C74C3EA2C1B90AF00039E77 /* ThemeManager.swift in Sources */, + CEB7E06F2C8779C800BC270C /* SheetWithDetents.swift in Sources */, 5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */, 5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */, 5CB634B129E5EFEA0066AD6B /* PasscodeView.swift in Sources */,