Переместить TextField вверх, когда клавиатура появилась с помощью SwiftUI?: iOS - PullRequest
5 голосов
/ 07 июня 2019

У меня есть семь TextField внутри моего основного ContentView.Когда пользователь открывает клавиатуру, некоторые из TextField скрыты под рамкой клавиатуры.Поэтому я хочу переместить все TextField вверх соответственно, когда появилась клавиатура.

Я использовал приведенный ниже код для добавления TextField на экран.

struct ContentView : View {
    @State var textfieldText: String = ""

    var body: some View {
            VStack {
                TextField($textfieldText, placeholder: Text("TextField1"))
                TextField($textfieldText, placeholder: Text("TextField2"))
                TextField($textfieldText, placeholder: Text("TextField3"))
                TextField($textfieldText, placeholder: Text("TextField4"))
                TextField($textfieldText, placeholder: Text("TextField5"))
                TextField($textfieldText, placeholder: Text("TextField6"))
                TextField($textfieldText, placeholder: Text("TextField6"))
                TextField($textfieldText, placeholder: Text("TextField7"))
            }
    }
}

Выход:

Output

Ответы [ 3 ]

8 голосов
/ 23 июня 2019

Вам не нужны отступы, ScrollViews или списки для достижения этой цели.Хотя это решение будет хорошо с ними играть.Я включаю два примера здесь.

Первый перемещается all textField вверх, если клавиатура появляется для любого из них.Но только при необходимости.Если клавиатура не скрывает текстовые поля, они не будут перемещаться.

Во втором примере представление только перемещается достаточно просто, чтобы избежать скрытия активного текстового поля.

Оба примера используют один и тот жеобщий код, найденный в конце: GeometryGetter и KeyboardGuardian

Первый пример (показать все текстовые поля)

enter image description here

struct ContentView: View {
    @ObjectBinding private var kGuardian = KeyboardGuardian(textFieldCount: 1)
    @State private var name = Array<String>.init(repeating: "", count: 3)

    var body: some View {

        VStack {
            Group {
                Text("Some filler text").font(.largeTitle)
                Text("Some filler text").font(.largeTitle)
            }

            TextField($name[0], placeholder: Text("enter text #1"))
                .textFieldStyle(.roundedBorder)

            TextField($name[1], placeholder: Text("enter text #2"))
                .textFieldStyle(.roundedBorder)

            TextField($name[2], placeholder: Text("enter text #3"))
                .textFieldStyle(.roundedBorder)
                .background(GeometryGetter(rect: $kGuardian.rects[0]))

        }.offset(y: kGuardian.slide).animation(.basic(duration: 1.0))
    }

}

Второй пример (показать только активное поле)

enter image description here

struct ContentView: View {
    @ObjectBinding private var kGuardian = KeyboardGuardian(textFieldCount: 3)
    @State private var name = Array<String>.init(repeating: "", count: 3)

    var body: some View {

        VStack {
            Group {
                Text("Some filler text").font(.largeTitle)
                Text("Some filler text").font(.largeTitle)
            }

            TextField($name[0], placeholder: Text("enter text #1"), onEditingChanged: { if $0 { self.kGuardian.showField = 0 } })
                .textFieldStyle(.roundedBorder)
                .background(GeometryGetter(rect: $kGuardian.rects[0]))

            TextField($name[1], placeholder: Text("enter text #2"), onEditingChanged: { if $0 { self.kGuardian.showField = 1 } })
                .textFieldStyle(.roundedBorder)
                .background(GeometryGetter(rect: $kGuardian.rects[1]))

            TextField($name[2], placeholder: Text("enter text #3"), onEditingChanged: { if $0 { self.kGuardian.showField = 2 } })
                .textFieldStyle(.roundedBorder)
                .background(GeometryGetter(rect: $kGuardian.rects[2]))

            }.offset(y: kGuardian.slide).animation(.basic(duration: 1.5))
    }

}

GeometryGetter

Thisэто представление, которое поглощает размер и положение родительского представления.Для этого он вызывается внутри модификатора .background.Это очень мощный модификатор, а не просто способ украсить фон представления.При передаче представления в .background (MyView ()) MyView получает измененное представление в качестве родительского.Использование GeometryReader - это то, что позволяет представлению узнать геометрию родителя.

Например: Text("hello").background(GeomtryGetter(rect: $bounds)) заполнит границы переменных с размером и положением текстового представления и с использованием глобальной координатыпробел.

struct GeometryGetter: View {
    @Binding var rect: CGRect

    var body: some View {
        GeometryReader { geometry in
            Group { () -> ShapeView<Rectangle, Color> in
                DispatchQueue.main.async {
                    self.rect = geometry.frame(in: .global)
                }

                return Rectangle().fill(Color.clear)
            }
        }
    }
}

Обновление Я добавил DispatchQueue.main.async, чтобы избежать возможности изменения состояния представления во время его визуализации.***

KeyboardGuardian

Цель KeyboardGuardian - отслеживать события показа / скрытия клавиатуры и вычислять, сколько места необходимо сместить.

Обновление: Я изменил KeyboardGuardian для обновления слайда, когда пользователь перемещается из одного поля в другое

import SwiftUI
import Combine

final class KeyboardGuardian: BindableObject {
    let didChange = PassthroughSubject<Void, Never>()

    public var rects: Array<CGRect>
    public var keyboardRect: CGRect = CGRect()

    // keyboardWillShow notification may be posted repeatedly,
    // this flag makes sure we only act once per keyboard appearance
    public var keyboardIsHidden = true

    public var slide: Length = 0 {
        didSet {
            didChange.send()
        }
    }

    public var showField: Int = 0 {
        didSet {
            updateSlide()
        }
    }

    init(textFieldCount: Int) {
        self.rects = Array<CGRect>(repeating: CGRect(), count: textFieldCount)

        NotificationCenter.default.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyBoardDidHide(notification:)), name: UIResponder.keyboardDidHideNotification, object: nil)

    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }

    @objc func keyBoardWillShow(notification: Notification) {
        if keyboardIsHidden {
            keyboardIsHidden = false
            if let rect = notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect {
                keyboardRect = rect
                updateSlide()
            }
        }
    }

    @objc func keyBoardDidHide(notification: Notification) {
        keyboardIsHidden = true
        updateSlide()
    }

    func updateSlide() {
        if keyboardIsHidden {
            slide = 0
        } else {
            let tfRect = self.rects[self.showField]
            let diff = keyboardRect.minY - tfRect.maxY

            if diff > 0 {
                slide += diff
            } else {
                slide += min(diff, 0)
            }

        }
    }
}
3 голосов
/ 07 июня 2019

Вам нужно добавить ScrollView и установить нижний отступ для размера клавиатуры, чтобы содержимое могло прокручиваться при появлении клавиатуры.

Чтобы получить размер клавиатуры, вам потребуетсяиспользовать NotificationCenter для регистрации на событие клавиатуры.Для этого вы можете использовать собственный класс:

final class KeyboardResponder: BindableObject {
    let didChange = PassthroughSubject<CGFloat, Never>()

    private var _center: NotificationCenter
    private(set) var currentHeight: CGFloat = 0 {
        didSet {
            didChange.send(currentHeight)
        }
    }

    init(center: NotificationCenter = .default) {
        _center = center
        _center.addObserver(self, selector: #selector(keyBoardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        _center.addObserver(self, selector: #selector(keyBoardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
    }

    deinit {
        _center.removeObserver(self)
    }

    @objc func keyBoardWillShow(notification: Notification) {
        print("keyboard will show")
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
            currentHeight = keyboardSize.height
        }
    }

    @objc func keyBoardWillHide(notification: Notification) {
        print("keyboard will hide")
        currentHeight = 0
    }
}

Соответствие BindableObject позволит вам использовать этот класс в качестве State и вызвать обновление представления.При необходимости посмотрите учебник для BindableObject: Учебник SwiftUI

Когда вы получите это, вам нужно настроить ScrollView, чтобы уменьшить его размер при появлении клавиатуры.Для удобства я обернул ScrollView в какой-то компонент:

struct KeyboardScrollView<Content: View>: View {
    @State var keyboard = KeyboardResponder()
    private var content: Content

    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    var body: some View {
        ScrollView {
            VStack {
                content
            }
        }
        .padding(.bottom, keyboard.currentHeight)
    }
}

Теперь все, что вам нужно сделать, это встроить ваш контент в пользовательский ScrollView.

struct ContentView : View {
    @State var textfieldText: String = ""

    var body: some View {
        KeyboardScrollView {
            ForEach(0...10) { index in
                TextField(self.$textfieldText, placeholder: Text("TextField\(index)")) {
                    // Hide keyboard when uses tap return button on keyboard.
                    self.endEditing(true)
                }
            }
        }
    }

    private func endEditing(_ force: Bool) {
        UIApplication.shared.keyWindow?.endEditing(true)
    }
}

Редактировать: Поведение прокрутки действительно странно, когда клавиатура прячется.Возможно, использование анимации для обновления отступов исправит это, или вам следует рассмотреть возможность использования чего-то другого, кроме padding, для настройки размера представления прокрутки.

0 голосов
/ 07 июля 2019

Самый элегантный ответ, который мне удалось найти, аналогичен решению Рафаэля.Создайте класс для прослушивания событий клавиатуры.Вместо того чтобы использовать размер клавиатуры для изменения отступов, верните отрицательное значение размера клавиатуры и используйте модификатор .offset (y :), чтобы отрегулировать смещение внешнего внешнего контейнера.Он оживляет достаточно хорошо и работает с любым видом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...