Итак, есть задача: переделать экран авторизации на новый дизайн, добавить новые способы авторизации. И заодно опять же перейти на SwiftUI. Кстати, у нас довольно объёмный проект, и я думала мы будем очень долго переходить с UIKit, но прошло пол года, а уже треть почти переделана, без особых напрягов.

Так вот, кусочек дизайна выглядит примерно так (все экраны нет смысла показывать, т к в данной статье хочу показать только злополучное модальное окно).

новый дизайн
новый дизайн

Есть: начальный экран с кнопками для входа и модальный экран с согласием на обработку данных. На первый взгляд никакого подвоха, всё должно быть нативно. 

Пишем код основной вью:

struct AuthView: View {
    
    @StateObject private var viewModel = AuthViewModel()
    @StateObject private var agreementViewModel = AuthAgreementModalViewModel()
        
    var body: some View {
        NavigationView {
            VStack(spacing: 0) {
                // Image
                Image("authPicture")
                    
                // Title
                Text("Quick login")
                
                // Кнопки мессенджеров
                VStack(spacing: 8) {
                  ...
                  // в действие по нажатию на кнопку добавляем
                  // появление модального окна:

                  agreementViewModel.showModal = true
                  
                }
                .padding(.top, 24)
                
                Spacer()
                
                // Дополнительный лейбл
                    Text("If you have any problems with authorization, please contact our support service support@domain.com")
                
            }
            .navigationBarHidden(true)
        }
    }
}

Добавляем ViewModel:

class AuthViewModel: ObservableObject {
    @Published vars ...
  
    var completionHandler: (() -> Void)?
    
    func handleLoginAfterAgreement() {
        guard let button = selectedButton else { return }
        handleLogin(for: button)
        selectedButton = nil
    }
    
    func handleLogin(for button: LoginButtonId) {
        switch button {
        case .apple:
            print("Apple ID login tapped")
            startSignInWithAppleFlow()
        case .gmail:
            print("gmail login tapped")
            // логику для gmail
        case .email:
            print("email login tapped")
            // логику для email
        }
    }
    
    func startSignInWithAppleFlow() {
        ...
    }
    
    func onFinish() {
        completionHandler?()
    }
}

Полный код будет на GitHub, там полностью всё расписала, в том числе и регистрацию через AppleID.

По нажатию на одну из кнопок регистрации нужно сначала показать окно с соглашением, и только после согласия пользователя с правилами - производить действие по регистрации.

Собственно, самое простое, как показать модальное окно:

.sheet(isPresented: $agreementViewModel.showModal) {
    AuthAgreementModalView()
}

Но что мы видим при такой имплементации:

Вроде всё норм, но модальное окно не подстраивается по высоте содержимого. Это легко решается с помощью: .presentationDetents

.sheet(isPresented: $agreementViewModel.showModal) {
    AuthAgreementModalView()
        .presentationDetents([.medium, .large]) // Опционально несколько размеров
        .presentationDragIndicator(.visible)   // Показывает индикатор перетаскивания
}

Так же, как в UIKit, сделать, к сожалению, нельзя. То есть нам всё равно придётся примерно выбрать высоту. Но на ios15 даже такое не работает. Поэтому возвращаемся к любимому костылю:

.overlay(
  Group {
    if agreementViewModel.showModal {
      Color(white: 0, opacity: 0.5)
        .edgesIgnoringSafeArea(.all)
      AuthAgreementModalView(viewModel: agreementViewModel, onDismiss: {
      // Обработчик закрытия модального окна
        agreementViewModel.showModal = false
        viewModel.handleLoginAfterAgreement()
      })
      .transition(.move(edge: .bottom))
      .zIndex(1)
    }
  }
)
.animation(.easeInOut, value: agreementViewModel.showModal)

Прописываем прозрачный серый бэкграунд, откуда появляется окно, а так же действие по закрытию. А в сам AuthAgreementModalView добавим скругление верхних углов .cornerRadius(24, corners: [.topLeft, .topRight])

Ну вот и всё, после скрытия модального окна открывается нужная регистрация:

Полный код тут: ссылка на GitHub.

Если интересно, подписывайтесь на мой ТГ, там выкладываю рилсы про разработку: канал тут.

Комментарии (2)


  1. Revolt-or-die
    17.01.2025 18:40

    А зачем поддерживать iOS 15?


    1. kosyakus Автор
      17.01.2025 18:40

      Аналитики проанализировали данные с Firebase по пользователям и пока весомый процент людей ещё ходит со старыми айфонами. Увы, я бы тоже давно отказалась.