import Combine import SwiftUI struct RoundedBackground: ViewModifier { private let color: Color init(color: Color = Color("CapsuleColor")) { self.color = color } func body(content: Content) -> some View { content .padding() .background( RoundedRectangle(cornerRadius: 8, style: .continuous) .fill() .foregroundColor(color) ) } } struct CapsulaBackground: ViewModifier { private let color: Color init(color: Color = Color("CapsuleColor")) { self.color = color } func body(content: Content) -> some View { content .padding() .background( Capsule() .fill() .foregroundColor(color) ) } } struct Link: ViewModifier where T: View { private let destination: T init(destination: T) { self.destination = destination } func body(content: Content) -> some View { ZStack { NavigationLink(destination: destination) { EmptyView() }.hidden() content } } } struct AdaptsToSoftwareKeyboard: ViewModifier { @State var currentHeight: CGFloat = 0 func body(content: Content) -> some View { content .padding(.bottom, currentHeight).animation(.easeOut(duration: 0.25)) .edgesIgnoringSafeArea(currentHeight == 0 ? Edge.Set() : .bottom) .onAppear(perform: subscribeToKeyboardChanges) } private let keyboardHeightOnOpening = NotificationCenter.default .publisher(for: UIResponder.keyboardWillShowNotification) .map { $0.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect } .map(\.height) private let keyboardHeightOnHiding = NotificationCenter.default .publisher(for: UIResponder.keyboardWillHideNotification) .map { _ in CGFloat(0) } private func subscribeToKeyboardChanges() { _ = Publishers.Merge(keyboardHeightOnOpening, keyboardHeightOnHiding) .subscribe(on: DispatchQueue.main) .sink { height in if self.currentHeight == 0 || height == 0 { self.currentHeight = height } } } } extension View { func roundedBackground() -> some View { modifier(RoundedBackground()) } func buttonBackground() -> some View { modifier(RoundedBackground(color: .accentColor)) } func navigationLink(to screen: Screen, from view: V) -> some View { modifier(Link(destination: view.viewModel.view(for: screen))) } func adaptsToSoftwareKeyboard() -> some View { modifier(AdaptsToSoftwareKeyboard()) } func modal(for screen: Screen?, from view: V) -> some View { onTapGesture { view.viewModel.showModal(for: screen) } } func asAny() -> AnyView { .init(self) } }