Klavye SwiftUI'de göründüğünde TextField'ı yukarı taşı


100

TextFieldAna bölümümde yedi tane var ContentView. Kullanıcı klavyeyi açtığında, TextFieldbazıları klavye çerçevesinin altında gizlenir. Bu yüzden TextFieldklavye göründüğünde sırasıyla hepsini yukarı taşımak istiyorum .

TextFieldEkrana eklemek için aşağıdaki kodu kullandım .

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"))
            }
    }
}

Çıktı:

Çıktı



1
@PrashantTukadiya Hızlı yanıt için teşekkürler. TextField'ı Scrollview içine ekledim ancak yine de aynı sorunla karşılaşıyorum.
Hitesh Surani

1
@DimaPaliychuk Bu işe yaramayacak. SwiftUI
Prashant Tukadiya

20
Klavyenin gösterilmesi ve ekrandaki içeriğin gizlenmesi neden ilk Objective C iPhone uygulaması? Bu sürekli çözülen bir sorundur . Ben, Apple'ın SwiftUi ile bu konuya değinmediği için hayal kırıklığına uğruyorum. Bu yorumun kimseye yardımcı olmadığını biliyorum, ancak bu sorunu gündeme getirmek istedim, gerçekten de Apple'a bir çözüm sağlaması için baskı uygulamalıyız ve topluluğun bu en yaygın sorunları her zaman tedarik etmesine güvenmemeliyiz.
P. Ent

Yanıtlar:


64

Xcode, beta 7 için güncellenen kod.

Bunu başarmak için dolguya, ScrollViews veya Listelere ihtiyacınız yoktur. Her ne kadar bu çözüm onlarla da iyi oynayacak. Buraya iki örnek ekliyorum.

İlki , herhangi biri için klavye görünüyorsa, tüm textField'ı yukarı taşır . Ama sadece gerekirse. Klavye metin alanlarını gizlemezse hareket etmezler.

İkinci örnekte, görünüm yalnızca etkin metin alanını gizlemekten kaçınmak için yeterince hareket eder.

Her iki örnek de, sonunda bulunan aynı ortak kodu kullanır: GeometryGetter ve KeyboardGuardian

İlk Örnek (tüm metin alanlarını göster)

Klavye açıldığında, 3 metin alanı yeterince yukarı taşınır ve ardından tümü görünür kalır

struct ContentView: View {
    @ObservedObject 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("enter text #1", text: $name[0])
                .textFieldStyle(RoundedBorderTextFieldStyle())

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

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

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

}

İkinci Örnek (sadece aktif alanı göster)

Her bir metin alanına tıklandığında, görünüm yalnızca tıklanan metin alanını görünür kılacak kadar yukarı taşınır.

struct ContentView: View {
    @ObservedObject 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("text #1", text: $name[0], onEditingChanged: { if $0 { self.kGuardian.showField = 0 } })
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .background(GeometryGetter(rect: $kGuardian.rects[0]))

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

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

            }.offset(y: kGuardian.slide).animation(.easeInOut(duration: 1.0))
    }.onAppear { self.kGuardian.addObserver() } 
.onDisappear { self.kGuardian.removeObserver() }

}

GeometryGetter

Bu, üst görünümünün boyutunu ve konumunu emen bir görünümdür. Bunu başarmak için, .background değiştiricisinin içinde çağrılır. Bu çok güçlü bir değiştiricidir, sadece bir görünümün arka planını dekore etmenin bir yolu değildir. Bir görünümü .background'a (MyView ()) aktarırken, MyView üst öğe olarak değiştirilmiş görünümü alıyor. GeometryReader'ı kullanmak, görünümün ebeveynin geometrisini bilmesini mümkün kılan şeydir.

Örneğin: Text("hello").background(GeometryGetter(rect: $bounds))Değişken sınırları Metin görünümünün boyutu ve konumuyla ve global koordinat alanını kullanarak doldurur.

struct GeometryGetter: View {
    @Binding var rect: CGRect

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

                return AnyView(Color.clear)
            }
        }
    }
}

Güncelleme Görünümün işlenirken durumunu değiştirme olasılığını önlemek için DispatchQueue.main.async'i ekledim.

KeyboardGuardian

KeyboardGuardian'ın amacı, klavye gösterme / gizleme olaylarını takip etmek ve görünümün ne kadar alan kaydırılması gerektiğini hesaplamaktır.

Güncelleme: Kullanıcı bir alandan diğerine sekme yaptığında slaytı yenilemek için KeyboardGuardian'ı değiştirdim

import SwiftUI
import Combine

final class KeyboardGuardian: ObservableObject {
    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

    @Published var slide: CGFloat = 0

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

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

    }

    func addObserver() {
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)
}

func removeObserver() {
 NotificationCenter.default.removeObserver(self)
}

    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)
            }

        }
    }
}

1
Protokole GeometryGetteruygun hale getirerek arka plandan çok görünüm değiştirici olarak eklemek mümkün müdür ViewModifier?
Sudara

2
Mümkün, ancak kazanç nedir? Bunu şu şekilde eklemelisiniz: .modifier(GeometryGetter(rect: $kGuardian.rects[1]))yerine .background(GeometryGetter(rect: $kGuardian.rects[1])). Pek bir fark yok (sadece 2 karakter daha az).
kontiki

3
Bazı durumlarda, bu ekrandan uzaklaşıyorsanız, yeni dikdörtgeni atarken GeometryGetter içindeki programdan bir SİNYAL DURDURMA alabilirsiniz. Böyle bir durumda, self.rect'e bir değer atamadan önce geometrinin boyutunun sıfırdan büyük olduğunu doğrulamak için bir kod ekleyin (geometry.size.width> 0 && geometry.size.height> 0)
Julio Bailon

1
Biliyorum neden ama hareketli değil @JulioBailon geometry.frameüzerinden DispatchQueue.main.asyncSİNYAL ABORT ile yardımcı hemen şimdi çözüm test edecek. Güncelleme: if geometry.size.width > 0 && geometry.size.height > 0atamadan önce self.rectyardımcı oldu.
Roman Vasilyev

2
bu benim için de self.rect = geometry.frame (in: .global) SIGNAL ABORT alma konusunda kırılıyor ve bu hatayı gidermek için önerilen tüm çözümleri denedi
Marwan Roushdy

55

@Rraphael'in çözümünü geliştirmek için, bugünün xcode11 swiftUI desteğiyle onu kullanılabilir hale getirdim.

import SwiftUI

final class KeyboardResponder: ObservableObject {
    private var notificationCenter: NotificationCenter
    @Published private(set) var currentHeight: CGFloat = 0

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

    deinit {
        notificationCenter.removeObserver(self)
    }

    @objc func keyBoardWillShow(notification: Notification) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
            currentHeight = keyboardSize.height
        }
    }

    @objc func keyBoardWillHide(notification: Notification) {
        currentHeight = 0
    }
}

Kullanım:

struct ContentView: View {
    @ObservedObject private var keyboard = KeyboardResponder()
    @State private var textFieldInput: String = ""

    var body: some View {
        VStack {
            HStack {
                TextField("uMessage", text: $textFieldInput)
            }
        }.padding()
        .padding(.bottom, keyboard.currentHeight)
        .edgesIgnoringSafeArea(.bottom)
        .animation(.easeOut(duration: 0.16))
    }
}

Yayınlanan currentHeight, bir kullanıcı arayüzünün yeniden oluşturulmasını tetikler ve klavye gösterildiğinde TextField'ınızı yukarı, kapatıldığında ise geri alır. Ancak ScrollView kullanmadım.


6
Bu cevabı basitliği için beğendim. .animation(.easeOut(duration: 0.16))Klavyenin yukarı kayma hızıyla eşleşmeyi denemek için ekledim .
Mark Moeykens

Klavye için neden maksimum 340 yükseklik ayarladınız?
Daniel Ryan

1
@DanielRyan Bazen klavye yüksekliği simülatörde yanlış değerler veriyordu. Şu anda sorunu
çözmenin

1
Bu sorunu kendim görmedim. Belki son sürümlerde düzeltilmiştir. Daha büyük klavyeler olması (veya olacağı) ihtimaline karşı boyutu kilitlemek istemedim.
Daniel Ryan

1
İle deneyebilirsin keyboardFrameEndUserInfoKey. Bu, klavyenin son çerçevesini tutmalıdır.
Mathias Claassen

50

Önerilen çözümlerin çoğunu denedim ve çoğu durumda işe yarasalar da bazı sorunlar yaşadım - özellikle güvenli alanla ilgili (TabView sekmesinin içinde bir Formum var).

Belirli bir görüntünün güvenli alan alt ekini elde etmek ve onu dolgu hesaplamasında kullanmak için birkaç farklı çözümü birleştirip GeometryReader'ı kullanmayı bıraktım:

import SwiftUI
import Combine

struct AdaptsToKeyboard: ViewModifier {
    @State var currentHeight: CGFloat = 0

    func body(content: Content) -> some View {
        GeometryReader { geometry in
            content
                .padding(.bottom, self.currentHeight)
                .animation(.easeOut(duration: 0.16))
                .onAppear(perform: {
                    NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillShowNotification)
                        .merge(with: NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillChangeFrameNotification))
                        .compactMap { notification in
                            notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect
                    }
                    .map { rect in
                        rect.height - geometry.safeAreaInsets.bottom
                    }
                    .subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))

                    NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillHideNotification)
                        .compactMap { notification in
                            CGFloat.zero
                    }
                    .subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))
                })
        }
    }
}

Kullanım:

struct MyView: View {
    var body: some View {
        Form {...}
        .modifier(AdaptsToKeyboard())
    }
}

7
Vay canına, bu GeometryReader ve ViewModifier ile en SwiftUI sürümü. Sevdim.
Mateusz

3
Bu çok kullanışlı ve zarif. Bunu yazdığınız için çok teşekkür ederim.
Danilo Campos

2
Klavyemin üzerinde küçük bir boş beyaz görüntü görüyorum. Bu görünüm GeometryReader Görünümüdür, arka plan rengini değiştirerek onayladım. GeometryReader'ın gerçek Görünümüm ve klavyem arasında neden gösterildiğine dair herhangi bir fikir.
user832

6
Klavye ile görünüme ikinci kez gittiğimde hatayı Thread 1: signal SIGABRTçevrimiçi alıyorum rect.height - geometry.safeAreaInsets.bottomve TextField. TextFieldİlk seferde tıklayıp tıklamam önemli değil. Uygulama hala çöküyor.
JLively

2
Nihayet çalışan bir şey! Apple neden bunu yapamıyor bizim için delilik !!
Dave Kozikowski

35

Klavye göründüğünde küçültmek için başka herhangi bir görünümü sarabilen bir Görünüm oluşturdum.

Oldukça basit. Klavye gösterme / gizleme etkinlikleri için yayıncılar oluşturuyoruz ve ardından bunları kullanarak abone oluyoruz onReceive. Bunun sonucunu klavyenin arkasında klavye boyutunda bir dikdörtgen oluşturmak için kullanıyoruz.

struct KeyboardHost<Content: View>: View {
    let view: Content

    @State private var keyboardHeight: CGFloat = 0

    private let showPublisher = NotificationCenter.Publisher.init(
        center: .default,
        name: UIResponder.keyboardWillShowNotification
    ).map { (notification) -> CGFloat in
        if let rect = notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect {
            return rect.size.height
        } else {
            return 0
        }
    }

    private let hidePublisher = NotificationCenter.Publisher.init(
        center: .default,
        name: UIResponder.keyboardWillHideNotification
    ).map {_ -> CGFloat in 0}

    // Like HStack or VStack, the only parameter is the view that this view should layout.
    // (It takes one view rather than the multiple views that Stacks can take)
    init(@ViewBuilder content: () -> Content) {
        view = content()
    }

    var body: some View {
        VStack {
            view
            Rectangle()
                .frame(height: keyboardHeight)
                .animation(.default)
                .foregroundColor(.clear)
        }.onReceive(showPublisher.merge(with: hidePublisher)) { (height) in
            self.keyboardHeight = height
        }
    }
}

Daha sonra görünümü şu şekilde kullanabilirsiniz:

var body: some View {
    KeyboardHost {
        viewIncludingKeyboard()
    }
}

viewGörüntünün içeriğini küçültmek yerine yukarı taşımak için, dikdörtgen içeren bir VStack'e koymak yerine dolgu veya ofset eklenebilir .


6
Bunun doğru cevap olduğunu düşünüyorum. Sadece küçük bir ayar yaptım: dikdörtgen yerine sadece dolgusunu değiştiriyorum self.viewve harika çalışıyor. Animasyonda hiç sorun yok
Tae

5
Teşekkürler! Mükemmel çalışıyor. @Taed'in dediği gibi, dolgu yaklaşımı kullanmak daha iyidir. Nihai sonuç şöyle olacaktırvar body: some View { VStack { view .padding(.bottom, keyboardHeight) .animation(.default) } .onReceive(showPublisher.merge(with: hidePublisher)) { (height) in self.keyboardHeight = height } }
fdelafuente

1
Daha az oy kullanılmasına rağmen bu en hızlı cevaptır. AnyView kullanan önceki yaklaşım, Metal hızlandırma yardımını kırar.
Nelson Cardaci

4
Bu harika bir çözüm, ancak buradaki ana sorun, yalnızca klavyenin düzenlemekte olduğunuz metin alanını gizlemesi durumunda görünümü yukarı taşıma yeteneğini kaybetmenizdir. Demek istediğim: birkaç metin alanı olan bir formunuz varsa ve ilkini en üstte düzenlemeye başlarsanız, muhtemelen yukarı çıkmasını istemezsiniz çünkü bu, ekrandan dışarı çıkacaktır.
superpuccio

Cevabı gerçekten beğendim, ancak diğer tüm cevaplar gibi, görünümünüz bir TabBar içindeyse veya Görünüm ekranın alt kısmıyla aynı hizada değilse işe yaramaz.
Ben Patch

25

Kullanımı gerçekten basit bir görünüm değiştirici oluşturdum.

Aşağıdaki kodla bir Swift dosyası ekleyin ve bu değiştiriciyi görünümlerinize ekleyin:

.keyboardResponsive()
import SwiftUI

struct KeyboardResponsiveModifier: ViewModifier {
  @State private var offset: CGFloat = 0

  func body(content: Content) -> some View {
    content
      .padding(.bottom, offset)
      .onAppear {
        NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { notif in
          let value = notif.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect
          let height = value.height
          let bottomInset = UIApplication.shared.windows.first?.safeAreaInsets.bottom
          self.offset = height - (bottomInset ?? 0)
        }

        NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { notif in
          self.offset = 0
        }
    }
  }
}

extension View {
  func keyboardResponsive() -> ModifiedContent<Self, KeyboardResponsiveModifier> {
    return modifier(KeyboardResponsiveModifier())
  }
}


Güzel. Paylaşım için teşekkürler.
on yıllardır

2
Gerekirse sadece ofset yapsaydı harika olurdu (yani klavye giriş öğesini kapsamıyorsa kaydırmayın). Sahip olmak güzel ...
on yıllar

Bu harika çalışıyor, teşekkürler. Çok temiz bir uygulama ve benim için sadece gerekirse kaydırır.
Joshua

Harika! Bunu neden Github'da veya başka bir yerde sağlamıyorsunuz? :) Veya bunu github.com/hackiftekhar/IQKeyboardManager'a önerebilirsiniz çünkü henüz tam bir SwiftUI desteğine sahip değillerdir
Schnodderbalken

Oryantasyon değişiklikleri ile iyi oynamayacak ve gerekip gerekmediğine bakılmaksızın dengelenecektir.
GrandSteph

16

Veya sadece IQKeyBoardManagerSwift'i kullanabilirsiniz

ve isteğe bağlı olarak, araç çubuğunu gizlemek ve klavye dışındaki herhangi bir görünümde klavyenin gizlenmesini etkinleştirmek için bunu uygulama temsilcinize ekleyebilir.

        IQKeyboardManager.shared.enableAutoToolbar = false
        IQKeyboardManager.shared.shouldShowToolbarPlaceholder = false
        IQKeyboardManager.shared.shouldResignOnTouchOutside = true
        IQKeyboardManager.shared.previousNextDisplayMode = .alwaysHide

Bu gerçekten benim için de (beklenmedik) yol. Katı.
HelloTimo

Bu çerçeve beklenenden daha iyi çalıştı. Paylaşım için teşekkürler!
Richard Poutier

1
SwiftUI'de benim için iyi çalışıyor - teşekkürler @DominatorVbN - iPad manzara modunda IQKeyboardManager.shared.keyboardDistanceFromTextFieldrahat bir boşluk elde etmek için 40'a yükseltmem gerekiyordu.
Richard Groves

Ayrıca IQKeyboardManager.shared.enable = trueklavyenin metin alanlarımı gizlemesini engellemek için ayarlamak zorunda kaldım . Her durumda bu en iyi çözümdür. Dikey olarak düzenlenmiş 4 alanım var ve diğer çözümler en alttaki alanım için işe yarayacak, ancak en üstteki alanı görüş alanının dışına itecek.
MisterEd

12

ScrollViewKlavye göründüğünde içeriğin kaydırılabilmesi için klavyenin boyutunun bir alt dolgusunu eklemeniz ve ayarlamanız gerekir .

Klavye boyutunu elde etmek NotificationCenteriçin klavyeler etkinliğine kaydolmak için simgesini kullanmanız gerekecek . Bunu yapmak için özel bir sınıf kullanabilirsiniz:

import SwiftUI
import Combine

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
    }
}

BindableObjectUyum Bir şekilde bu sınıfı kullanmanızı sağlayacak Stateve görünümü güncelleme işlemini tetikleyebilir. Gerekirse şu eğitime bakın BindableObject: SwiftUI öğreticisi

Bunu aldığınızda ScrollView, klavye göründüğünde boyutunu küçültmek için a yapılandırmanız gerekir . Kolaylık sağlamak için bunu ScrollViewbir tür bileşene sardım:

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)
    }
}

Şimdi tek yapmanız gereken, içeriğinizi özelliğin içine yerleştirmek 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)
    }
}

Düzenleme: Klavye gizlenirken kaydırma davranışı gerçekten tuhaftır. Belki dolguyu güncellemek için bir animasyon kullanmak bunu düzeltir veya paddingkaydırma görünümü boyutunu ayarlamak için dışında başka bir şey kullanmayı düşünmelisiniz .


hey, bağlanabilir nesne konusunda deneyiminiz var gibi görünüyor. İstediğim gibi çalıştıramıyorum.
Şuraya

Neden @ObjectBinding'i kullanmıyorsunuz
SwiftiSwift

3
Kullanımdan BindableObjectkaldırıldığında, bu maalesef artık çalışmıyor.
LinusGeffarth

2
@LinusGeffarth Değeri ne olursa olsun BindableObject, sadece ObservableObjectve didChangeolarak yeniden adlandırıldı objectWillChange. Nesne görünümü gayet iyi güncelliyor ( @ObservedObjectbunun yerine kullanarak test ettim @State)
SeizeTheDay

Merhaba, bu çözüm içeriği kaydırıyor, ancak klavyenin üzerinde metin alanının yarısını gizleyen beyaz bir alan gösteriyor. Lütfen beyaz alanı nasıl kaldırabileceğimizi bana bildirin.
Shahbaz Sajjad

12

Xcode 12 - Tek satırlı kod

Bu değiştiriciyi TextField

.ignoresSafeArea(.keyboard, edges: .bottom)

Demo

Apple, klavyeyi güvenli alan için bir bölge olarak ekledi, böylece klavyeyi diğer bölgeler gibi klavyeyle herhangi birView yere taşımak için kullanabilirsiniz .


TextField için çalışıyor, peki ya TextEditor?
Андрей Первушин

Bu çalışır Herhangi View dahil TextEditor.
Mojtaba Hosseini

3
yeni test edildi, Xcode beta 6, TextEditor çalışmıyor
Андрей Первушин

1
Bir soru sorabilir ve buraya bağlayabilirsiniz. Böylece yeniden üretilebilir kodunuza bir göz atabilir ve yardım edip edemeyeceğimi görebilirim :) @kyrers
Mojtaba Hosseini

2
Ben kendim çözdüm. .ignoresSafeArea(.keyboard)Görünümünüze ekleyin .
leonboe1

6

Mevcut çözümleri gözden geçirip .keyboardAware()değiştirici sağlayan kullanışlı bir SPM paketinde yeniden düzenledim:

KeyboardAwareSwiftUI

Misal:

struct KeyboardAwareView: View {
    @State var text = "example"

    var body: some View {
        NavigationView {
            ScrollView {
                VStack(alignment: .leading) {
                    ForEach(0 ..< 20) { i in
                        Text("Text \(i):")
                        TextField("Text", text: self.$text)
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                            .padding(.bottom, 10)
                    }
                }
                .padding()
            }
            .keyboardAware()  // <--- the view modifier
            .navigationBarTitle("Keyboard Example")
        }

    }
}

Kaynak:

import UIKit
import SwiftUI

public class KeyboardInfo: ObservableObject {

    public static var shared = KeyboardInfo()

    @Published public var height: CGFloat = 0

    private init() {
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged), name: UIApplication.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged), name: UIResponder.keyboardWillHideNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardChanged), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    }

    @objc func keyboardChanged(notification: Notification) {
        if notification.name == UIApplication.keyboardWillHideNotification {
            self.height = 0
        } else {
            self.height = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0
        }
    }

}

struct KeyboardAware: ViewModifier {
    @ObservedObject private var keyboard = KeyboardInfo.shared

    func body(content: Content) -> some View {
        content
            .padding(.bottom, self.keyboard.height)
            .edgesIgnoringSafeArea(self.keyboard.height > 0 ? .bottom : [])
            .animation(.easeOut)
    }
}

extension View {
    public func keyboardAware() -> some View {
        ModifiedContent(content: self, modifier: KeyboardAware())
    }
}

Metin görünümünün yüksekliğinin yarısını görüyorum. bunu nasıl çözeceğini biliyor musun?
Matrosov Alexander

İyi. bu benim zamanımı kurtardı. Bunu kullanmadan önce, bu değiştiricinin görünümün alt dolgusunu ele aldığını biliyoruz.
Brownsoo Han

5

Benjamin Kindle'ın cevabını başlangıç ​​noktası olarak kullandım, ancak ele almak istediğim birkaç sorun vardı.

  1. Buradaki yanıtların çoğu, klavyenin çerçevesini değiştirmesiyle ilgilenmez, bu nedenle kullanıcı, cihazı ekrandaki klavyeyle döndürürse kırılırlar. keyboardWillChangeFrameNotificationİşlenen bildirimler listesine eklenmesi bunu giderir.
  2. Benzer ancak farklı harita kapanışları olan birden fazla yayıncı istemedim, bu yüzden üç klavye bildirimini de tek bir yayıncıya zincirledim. Kuşkusuz uzun bir zincir ama her adım oldukça basit.
  3. Ben sağlanan initbir kabul işlevini @ViewBuilderkullanabilirsiniz böylece KeyboardHostbir parametre olarak içerik görünümü geçmeye karşıt olarak, bir eğik kapatılması içeriğinizi geçmesi sadece başka View gibi görünüm ve init.
  4. Tae ve fdelafuente'nin yorumlarda önerdiği gibi Rectangle, alt dolguyu ayarlamak için değiştirdim.
  5. Sabit kodlu "UIKeyboardFrameEndUserInfoKey" dizesini kullanmak yerine, UIWindowolarak sağlanan dizeleri kullanmak istedim UIWindow.keyboardFrameEndUserInfoKey.

Hepsini bir araya getirerek sahip olduğum:

struct KeyboardHost<Content>: View  where Content: View {
    var content: Content

    /// The current height of the keyboard rect.
    @State private var keyboardHeight = CGFloat(0)

    /// A publisher that combines all of the relevant keyboard changing notifications and maps them into a `CGFloat` representing the new height of the
    /// keyboard rect.
    private let keyboardChangePublisher = NotificationCenter.Publisher(center: .default,
                                                                       name: UIResponder.keyboardWillShowNotification)
        .merge(with: NotificationCenter.Publisher(center: .default,
                                                  name: UIResponder.keyboardWillChangeFrameNotification))
        .merge(with: NotificationCenter.Publisher(center: .default,
                                                  name: UIResponder.keyboardWillHideNotification)
            // But we don't want to pass the keyboard rect from keyboardWillHide, so strip the userInfo out before
            // passing the notification on.
            .map { Notification(name: $0.name, object: $0.object, userInfo: nil) })
        // Now map the merged notification stream into a height value.
        .map { ($0.userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect ?? .zero).size.height }
        // If you want to debug the notifications, swap this in for the final map call above.
//        .map { (note) -> CGFloat in
//            let height = (note.userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect ?? .zero).size.height
//
//            print("Received \(note.name.rawValue) with height \(height)")
//            return height
//    }

    var body: some View {
        content
            .onReceive(keyboardChangePublisher) { self.keyboardHeight = $0 }
            .padding(.bottom, keyboardHeight)
            .animation(.default)
    }

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

struct KeyboardHost_Previews: PreviewProvider {
    static var previews: some View {
        KeyboardHost {
            TextField("TextField", text: .constant("Preview text field"))
        }
    }
}


bu çözüm işe yaramıyor, Keyboardyüksekliği artırıyor
GSerjo

@GSerjo'da gördüğünüz sorunları detaylandırır mısınız? Bu kodu uygulamamda kullanıyorum ve benim için iyi çalışıyor.
Timothy Sanders

Eğer açmak misiniz PridictiveiOS'taki keyboard. Settings-> General-> Keyboard-> Pridictive. bu durumda kireçlemeyi düzeltmez ve klavyeye dolgu ekler
GSerjo

@GSerjo: iOS 13.1 beta çalıştıran bir iPad Touch (7. nesil) üzerinde Tahminli metni etkinleştirdim. Tahmin satırının yüksekliği için doğru bir şekilde dolgu ekler. (Unutulmaması gereken önemli, burada klavyenin yüksekliğini ayarlamıyorum , görünümün kendi dolgusuna ekliyorum .) Yorumlanan hata ayıklama haritasında yer değiştirmeyi deneyin ve tahmin için aldığınız değerlerle oynayın tuş takımı. Başka bir yoruma bir günlük göndereceğim.
Timothy Sanders

"Hata ayıklama" haritasının açıklaması kaldırıldığında, atanan değeri görebilirsiniz keyboardHeight. İPod Touch'ımda (dikeyde) tahmini açık bir klavye 254 puandır. Onsuz 216 puan. Ekrandaki bir klavye ile tahmini kapatabilirim ve dolgu düzgün şekilde güncellenir. Tahmine dayalı klavye ekleme: Received UIKeyboardWillChangeFrameNotification with height 254.0 Received UIKeyboardWillShowNotification with height 254.0 Tahmine dayalı metni kapattığımda:Received UIKeyboardWillChangeFrameNotification with height 216.0
Timothy Sanders

4

Bu, @kontiki'nin inşa ettiği şeyden uyarlanmıştır. Beta 8 / GM seed altındaki bir uygulamada çalıştırıyorum, burada kaydırılması gereken alan bir NavigationView içindeki bir formun parçası. İşte KeyboardGuardian:

//
//  KeyboardGuardian.swift
//
//  /programming/56491881/move-textfield-up-when-thekeyboard-has-appeared-by-using-swiftui-ios
//

import SwiftUI
import Combine

/// The purpose of KeyboardGuardian, is to keep track of keyboard show/hide events and
/// calculate how much space the view needs to be shifted.
final class KeyboardGuardian: ObservableObject {
    let objectWillChange = ObservableObjectPublisher() // 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
    private var keyboardIsHidden = true

    var slide: CGFloat = 0 {
        didSet {
            objectWillChange.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)

    }

    @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 {
            slide = -keyboardRect.size.height
        }
    }
}

Ardından, rects dizisindeki yuvaları ve toplam sayıyı izlemek için bir enum kullandım:

enum KeyboardSlots: Int {
    case kLogPath
    case kLogThreshold
    case kDisplayClip
    case kPingInterval
    case count
}

KeyboardSlots.count.rawValuegerekli dizi kapasitesidir; diğerleri rawValue olarak .background (GeometryGetter) çağrıları için kullanacağınız uygun dizini verir.

Bu kurulumla, KeyboardGuardian'da bununla görünümler elde edilir:

@ObservedObject private var kGuardian = KeyboardGuardian(textFieldCount: SettingsFormBody.KeyboardSlots.count.rawValue)

Gerçek hareket şu şekildedir:

.offset(y: kGuardian.slide).animation(.easeInOut(duration: 1))

görünüme eklenir. Benim durumumda, tüm NavigationView'e eklenir, bu nedenle tüm montaj, klavye göründüğünde yukarı kayar.

SwiftUI ile bir Bitti araç çubuğu veya ondalık klavyede bir dönüş tuşu alma sorununu çözmedim, bunun yerine bunu başka bir yerde bir dokunuşta gizlemek için kullanıyorum:

struct DismissingKeyboard: ViewModifier {
    func body(content: Content) -> some View {
        content
            .onTapGesture {
                let keyWindow = UIApplication.shared.connectedScenes
                        .filter({$0.activationState == .foregroundActive})
                        .map({$0 as? UIWindowScene})
                        .compactMap({$0})
                        .first?.windows
                        .filter({$0.isKeyWindow}).first
                keyWindow?.endEditing(true)                    
        }
    }
}

Bunu bir görünüme şu şekilde eklersiniz:

.modifier(DismissingKeyboard())

Bazı görünümler (örneğin, seçiciler) bunun eklenmesinden hoşlanmaz, bu nedenle, en dış görünüme tokatlamak yerine, değiştiriciyi nasıl ekleyeceğiniz konusunda biraz daha ayrıntılı olmanız gerekebilir.

Sıkı çalışma için @ kontiki'ye çok teşekkürler. Yine de yukarıdaki GeometryGetter'ına ihtiyacınız olacak (hayır, bunu tercihleri ​​kullanacak şekilde dönüştürmek için çalışmadım) örneklerinde gösterdiği gibi.


1
Olumsuz oy veren kişiye: neden? Faydalı bir şey eklemeye çalıştım, bu yüzden sizin görüşünüze göre nasıl yanlış yaptığımı öğrenmek istiyorum
Feldur

4

Yukarıdaki çözümlerden birkaçının bazı sorunları vardı ve mutlaka "en temiz" yaklaşım değildi. Bu nedenle, aşağıdaki uygulama için birkaç şeyi değiştirdim.

extension View {
    func onKeyboard(_ keyboardYOffset: Binding<CGFloat>) -> some View {
        return ModifiedContent(content: self, modifier: KeyboardModifier(keyboardYOffset))
    }
}

struct KeyboardModifier: ViewModifier {
    @Binding var keyboardYOffset: CGFloat
    let keyboardWillAppearPublisher = NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)
    let keyboardWillHidePublisher = NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)

    init(_ offset: Binding<CGFloat>) {
        _keyboardYOffset = offset
    }

    func body(content: Content) -> some View {
        return content.offset(x: 0, y: -$keyboardYOffset.wrappedValue)
            .animation(.easeInOut(duration: 0.33))
            .onReceive(keyboardWillAppearPublisher) { notification in
                let keyWindow = UIApplication.shared.connectedScenes
                    .filter { $0.activationState == .foregroundActive }
                    .map { $0 as? UIWindowScene }
                    .compactMap { $0 }
                    .first?.windows
                    .filter { $0.isKeyWindow }
                    .first

                let yOffset = keyWindow?.safeAreaInsets.bottom ?? 0

                let keyboardFrame = (notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? .zero

                self.$keyboardYOffset.wrappedValue = keyboardFrame.height - yOffset
        }.onReceive(keyboardWillHidePublisher) { _ in
            self.$keyboardYOffset.wrappedValue = 0
        }
    }
}
struct RegisterView: View {
    @State var name = ""
    @State var keyboardYOffset: CGFloat = 0

    var body: some View {

        VStack {
            WelcomeMessageView()
            TextField("Type your name...", text: $name).bordered()
        }.onKeyboard($keyboardYOffset)
            .background(WelcomeBackgroundImage())
            .padding()
    }
}

Daha temiz bir yaklaşım ve içeriği nasıl dengeleyeceğime dair sorumluluğu yapılandırılmış görünüme (değiştiriciye değil) taşımak isterdim, ancak görünüme göre ofset kodunu görünüme taşırken yayıncıların düzgün şekilde tetiklemesini sağlayamadım. ...

Ayrıca, final classşu anda bilinmeyen istisna çökmelerine neden olduğu için (arayüz gereksinimlerini karşılasa bile) ve genel olarak bir ScrollView, ofset kodunu uygularken en iyi yaklaşım olduğu için Publishers'ın bu durumda kullanılması gerektiğini unutmayın .


Çok güzel bir çözüm, kesinlikle tavsiye ederim! Klavyenin şu anda aktif olup olmadığını belirtmek için bir Bool ekledim.
Peanutsmasher

4

Dürüst olmak gerekirse bu cevapların çoğu gerçekten şişkin görünüyor. SwiftUI kullanıyorsanız, Combine'ı da kullanabilirsiniz.

KeyboardResponderAşağıda gösterildiği gibi bir oluşturun , ardından daha önce gösterildiği gibi kullanabilirsiniz.

İOS 14 için güncellendi.

import Combine
import UIKit

final class KeyboardResponder: ObservableObject {

    @Published var keyboardHeight: CGFloat = 0

    init() {
        NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)
            .compactMap { notification in
                (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height
            }
            .receive(on: DispatchQueue.main)
            .assign(to: \.keyboardHeight)
    }
}


struct ExampleView: View {
    @ObservedObject private var keyboardResponder = KeyboardResponder()
    @State private var text: String = ""

    var body: some View {
        VStack {
            Text(text)
            Spacer()
            TextField("Example", text: $text)
        }
        .padding(.bottom, keyboardResponder.keyboardHeight)
    }
}

Klavyenin göründüğü animasyonla eşleştirmek için bir .animation (.easeIn) ekledim
Michiel Pelt

(İOS 13 için bu cevabın geçmişine gidin)
Loris Foe

Merhaba, .assign (to: \ .keyboardHeight) bu hatayı veriyor "Anahtar yolu türü bağlamdan çıkarılamıyor; açıkça bir kök türü belirtmeyi düşünün". Lütfen bana hem ios 13 hem de ios 14 için doğru ve temiz çözümü bildirin.
Shahbaz Sajjad

3

SwiftUI için geçiş / animasyon API tamamlandıktan olmadığından emin değilim, ama sen kullanabilirsiniz CGAffineTransformile.transformEffect

Aşağıdaki gibi yayınlanmış bir özelliğe sahip gözlemlenebilir bir klavye nesnesi oluşturun:

    final class KeyboardResponder: ObservableObject {
    private var notificationCenter: NotificationCenter
    @Published var readyToAppear = false

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

    deinit {
        notificationCenter.removeObserver(self)
    }

    @objc func keyBoardWillShow(notification: Notification) {
        readyToAppear = true
    }

    @objc func keyBoardWillHide(notification: Notification) {
        readyToAppear = false
    }

}

görünümünüzü şu şekilde yeniden düzenlemek için bu özelliği kullanabilirsiniz:

    struct ContentView : View {
    @State var textfieldText: String = ""
    @ObservedObject private var keyboard = KeyboardResponder()

    var body: some View {
        return self.buildContent()
    }

    func buildContent() -> some View {
        let mainStack = VStack {
            TextField("TextField1", text: self.$textfieldText)
            TextField("TextField2", text: self.$textfieldText)
            TextField("TextField3", text: self.$textfieldText)
            TextField("TextField4", text: self.$textfieldText)
            TextField("TextField5", text: self.$textfieldText)
            TextField("TextField6", text: self.$textfieldText)
            TextField("TextField7", text: self.$textfieldText)
        }
        return Group{
            if self.keyboard.readyToAppear {
                mainStack.transformEffect(CGAffineTransform(translationX: 0, y: -200))
                    .animation(.spring())
            } else {
                mainStack
            }
        }
    }
}

veya daha basit

VStack {
        TextField("TextField1", text: self.$textfieldText)
        TextField("TextField2", text: self.$textfieldText)
        TextField("TextField3", text: self.$textfieldText)
        TextField("TextField4", text: self.$textfieldText)
        TextField("TextField5", text: self.$textfieldText)
        TextField("TextField6", text: self.$textfieldText)
        TextField("TextField7", text: self.$textfieldText)
    }.transformEffect(keyboard.readyToAppear ? CGAffineTransform(translationX: 0, y: -50) : .identity)
            .animation(.spring())

Bu yanıtı seviyorum, ancak 'ScreenSize.portrait'in nereden geldiğinden tam olarak emin değilim.
Misha Taş

Merhaba @MishaStone teşekkürler, yaklaşımımı seç. ScreenSize.portrait, Oryantasyon ve yüzde ekran tabanının ölçümlerini elde etmek için yaptığım bir sınıftır .... ancak çeviriniz için istediğiniz herhangi bir değerle değiştirebilirsiniz
blacktiago

3

Xcode 12 beta 4 ignoresSafeArea, artık klavyeden kaçınmak için kullanabileceğiniz yeni bir görünüm değiştirici ekler .

.ignoresSafeArea([], edges: [])

Bu, klavyeyi ve tüm güvenli alan kenarlarını önler. .keyboardÖnlenmesini istemiyorsanız ilk parametreyi olarak ayarlayabilirsiniz . En azından benim görüşüme göre, bazı tuhaflıklar var, ancak Apple'ın klavyeden kaçınmamızı istediği yol bu.


2

Cevap buradan kopyalandı: TextField, SwiftUI ile her zaman klavyenin üstünde

Farklı yaklaşımlar denedim ve hiçbiri benim için işe yaramadı. Aşağıdaki, farklı cihazlar için çalışan tek cihazdır.

Bu uzantıyı bir dosyaya ekleyin:

import SwiftUI
import Combine

extension View {
    func keyboardSensible(_ offsetValue: Binding<CGFloat>) -> some View {
        
        return self
            .padding(.bottom, offsetValue.wrappedValue)
            .animation(.spring())
            .onAppear {
                NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { notification in
                    
                    let keyWindow = UIApplication.shared.connectedScenes
                        .filter({$0.activationState == .foregroundActive})
                        .map({$0 as? UIWindowScene})
                        .compactMap({$0})
                        .first?.windows
                        .filter({$0.isKeyWindow}).first
                    
                    let bottom = keyWindow?.safeAreaInsets.bottom ?? 0
                    
                    let value = notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect
                    let height = value.height
                    
                    offsetValue.wrappedValue = height - bottom
                }
                
                NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { _ in
                    offsetValue.wrappedValue = 0
                }
        }
    }
}

Sizin görüşünüzde, offsetValue:

struct IncomeView: View {

  @State private var offsetValue: CGFloat = 0.0

  var body: some View { 
    
    VStack {
     //...       
    }
    .keyboardSensible($offsetValue)
  }
}

2
Bilginize, aradığınızda nesnelere NotificationCenter.default.addObserversahipsiniz ... Bunları saklamanız ve uygun bir zamanda gözlemcileri kaldırmanız gerekir ...
TheCodingArt

Merhaba @TheCodingArt, doğru bunu böyle yapmaya çalıştım ( oleb.net/blog/2018/01/notificationcenter-removeobserver ) ama benim için işe yaramıyor gibi görünüyor, herhangi bir fikir?
Ray

2

Mark Krenek ve Heiko'nun da belirttiği gibi, Apple bu sorunu en sonunda Xcode 12 beta 4'te ele alıyor gibiydi. İşler hızla ilerliyor. 18 Ağustos 2020'de yayınlanan Xcode 12 beta 5 sürüm notlarına göre "Form, List ve TextEditor artık klavyenin arkasındaki içeriği gizlemiyor. (66172025)". Sadece indirdim ve birkaç gün önce başlattığım bir uygulamadaki Form kapsayıcıyla beta 5 simülatöründe (iPhone SE2) hızlı bir test yaptım.

Artık bir TextField için "sadece çalışıyor" . SwiftUI, klavyeye yer açmak için kapsülleyici Form'a otomatik olarak uygun alt dolguyu sağlayacaktır. Ve klavyenin hemen üzerindeki TextField'ı görüntülemek için Formu otomatik olarak yukarı kaydıracaktır. ScrollView konteyneri artık klavye açıldığında da iyi davranıyor.

Ancak, Андрей Первушин'ın bir yorumda işaret ettiği gibi , TextEditor ile ilgili bir sorun var . Beta 5 & 6, klavye için yer açmak üzere kapsülleyici Form'a uygun alt dolguyu otomatik olarak sağlayacaktır. Ancak, Formu otomatik olarak yukarı KAYDIRMAZ. Klavye, TextEditor'ı kapsayacaktır. Dolayısıyla, TextField'den farklı olarak, kullanıcının TextEditor'ı görünür hale getirmek için Formu kaydırması gerekir. Bir hata raporu sunacağım. Belki Beta 7 bunu düzeltir. Çok yakın …

https://developer.apple.com/documentation/ios-ipados-release-notes/ios-ipados-14-beta-release-notes/


beta5 ve beta6'da test edilen elma sürüm notlarını görüyorum, TextField çalışıyor, TextEditor NOT, neyi özlüyorum? @State var text = "" var body: bazı Görünüm {Form {Bölüm {Metin (metin) .frame (yükseklik: 500)} Bölüm {Metin Alanı ("5555", metin: $ metin) .frame (yükseklik: 50)} Bölüm {TextEditor (metin: $ text) .frame (yükseklik: 120)}}}
Андрей Первушин

2

Kullanım:

import SwiftUI

var body: some View {
    ScrollView {
        VStack {
          /*
          TextField()
          */
        }
    }.keyboardSpace()
}

Kod:

import SwiftUI
import Combine

let keyboardSpaceD = KeyboardSpace()
extension View {
    func keyboardSpace() -> some View {
        modifier(KeyboardSpace.Space(data: keyboardSpaceD))
    }
}

class KeyboardSpace: ObservableObject {
    var sub: AnyCancellable?
    
    @Published var currentHeight: CGFloat = 0
    var heightIn: CGFloat = 0 {
        didSet {
            withAnimation {
                if UIWindow.keyWindow != nil {
                    //fix notification when switching from another app with keyboard
                    self.currentHeight = heightIn
                }
            }
        }
    }
    
    init() {
        subscribeToKeyboardEvents()
    }
    
    private let keyboardWillOpen = NotificationCenter.default
        .publisher(for: UIResponder.keyboardWillShowNotification)
        .map { $0.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect }
        .map { $0.height - (UIWindow.keyWindow?.safeAreaInsets.bottom ?? 0) }
    
    private let keyboardWillHide =  NotificationCenter.default
        .publisher(for: UIResponder.keyboardWillHideNotification)
        .map { _ in CGFloat.zero }
    
    private func subscribeToKeyboardEvents() {
        sub?.cancel()
        sub = Publishers.Merge(keyboardWillOpen, keyboardWillHide)
            .subscribe(on: RunLoop.main)
            .assign(to: \.self.heightIn, on: self)
    }
    
    deinit {
        sub?.cancel()
    }
    
    struct Space: ViewModifier {
        @ObservedObject var data: KeyboardSpace
        
        func body(content: Content) -> some View {
            VStack(spacing: 0) {
                content
                
                Rectangle()
                    .foregroundColor(Color(.clear))
                    .frame(height: data.currentHeight)
                    .frame(maxWidth: .greatestFiniteMagnitude)

            }
        }
    }
}

extension UIWindow {
    static var keyWindow: UIWindow? {
        let keyWindow = UIApplication.shared.connectedScenes
            .filter({$0.activationState == .foregroundActive})
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?.windows
            .filter({$0.isKeyWindow}).first
        return keyWindow
    }
}

çözümünüzü denediniz ... görünüm metin alanının yalnızca yarısına kaydırılır. Yukarıdaki tüm çözümü denedim. Aynı sorunu var. Lütfen yardım et!!!
Sona

@Zeona, basit bir uygulamayı dene, farklı bir şey yapıyor olabilirsin. Ayrıca, güvenli alan kullanıyorsanız '- (UIWindow.keyWindow? .SafeAreaInsets.bottom ?? 0)' öğesini kaldırmayı deneyin.
8suhas

kaldırıldı (UIWindow.keyWindow? .safeAreaInsets.bottom ?? 0) sonra klavyenin üzerinde beyaz bir boşluk görüyorum
Sona

1

Handling TabView's

Benjamin Kindle'ın cevabını beğendim ama TabViews'ı desteklemiyor. TabView'ları işlemek için kodunda yaptığım ayarlamam:

  1. UITabViewÇerçeve ayarlandığında tabView boyutunu depolamak için öğesine bir uzantı ekleyin . Bunu statik bir değişkende saklayabiliriz çünkü bir projede genellikle yalnızca bir tabView vardır (sizinki birden fazla ise, o zaman ayarlamanız gerekir).
extension UITabBar {

    static var size: CGSize = .zero

    open override var frame: CGRect {
        get {
            super.frame
        } set {
            UITabBar.size = newValue.size
            super.frame = newValue
        }
    }
}
  1. Sekme Çubuğunun yüksekliğini hesaba katmak için görünümün onReceivealt kısmında bunu değiştirmeniz gerekir KeyboardHost:
.onReceive(showPublisher.merge(with: hidePublisher)) { (height) in
            self.keyboardHeight = max(height - UITabBar.size.height, 0)
        }
  1. Ve bu kadar! Süper basit 🎉.

1

Aşağıdakileri genişleterek UIHostingControllerve ayarlayarak tamamen farklı bir yaklaşım benimsedim additionalSafeAreaInsets:

class MyHostingController<Content: View>: UIHostingController<Content> {
    override init(rootView: Content) {
        super.init(rootView: rootView)
    }

    @objc required dynamic init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        NotificationCenter.default.addObserver(self, 
                                               selector: #selector(keyboardDidShow(_:)), 
                                               name: UIResponder.keyboardDidShowNotification,
                                               object: nil)
        NotificationCenter.default.addObserver(self, 
                                               selector: #selector(keyboardWillHide), 
                                               name: UIResponder.keyboardWillHideNotification, 
                                               object: nil)
    }       

    @objc func keyboardDidShow(_ notification: Notification) {
        guard let info:[AnyHashable: Any] = notification.userInfo,
            let frame = info[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else {
                return
        }

        // set the additionalSafeAreaInsets
        let adjustHeight = frame.height - (self.view.safeAreaInsets.bottom - self.additionalSafeAreaInsets.bottom)
        self.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: adjustHeight, right: 0)

        // now try to find a UIResponder inside a ScrollView, and scroll
        // the firstResponder into view
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { 
            if let firstResponder = UIResponder.findFirstResponder() as? UIView,
                let scrollView = firstResponder.parentScrollView() {
                // translate the firstResponder's frame into the scrollView's coordinate system,
                // with a little vertical padding
                let rect = firstResponder.convert(firstResponder.frame, to: scrollView)
                    .insetBy(dx: 0, dy: -15)
                scrollView.scrollRectToVisible(rect, animated: true)
            }
        }
    }

    @objc func keyboardWillHide() {
        self.additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    }
}

/// IUResponder extension for finding the current first responder
extension UIResponder {
    private struct StaticFirstResponder {
        static weak var firstResponder: UIResponder?
    }

    /// find the current first responder, or nil
    static func findFirstResponder() -> UIResponder? {
        StaticFirstResponder.firstResponder = nil
        UIApplication.shared.sendAction(
            #selector(UIResponder.trap),
            to: nil, from: nil, for: nil)
        return StaticFirstResponder.firstResponder
    }

    @objc private func trap() {
        StaticFirstResponder.firstResponder = self
    }
}

/// UIView extension for finding the receiver's parent UIScrollView
extension UIView {
    func parentScrollView() -> UIScrollView? {
        if let scrollView = self.superview as? UIScrollView {
            return scrollView
        }

        return superview?.parentScrollView()
    }
}

Daha sonra yerine SceneDelegatekullanmak üzere değiştirin .MyHostingControllerUIHostingController

Bu bittiğinde, SwiftUI kodumun içindeki klavye hakkında endişelenmeme gerek yok.

(Not: Bunu yapmanın olası sonuçlarını tam olarak anlamak için henüz bunu yeterince kullanmadım!)


1

SwiftUI'de klavyeyi bu şekilde kullanıyorum. Hatırlanması gereken şey, hesaplamaları bağlı olduğu VStack'te yapıyor olmasıdır.

Bunu bir Görünümde Değiştirici olarak kullanırsınız. Bu yoldan:

struct LogInView: View {

  var body: some View {
    VStack {
      // Your View
    }
    .modifier(KeyboardModifier())
  }
}

Bu değiştiriciye gelmek için, önce VStack'te seçilen TextField konumunu almak için bir UIResponder uzantısı oluşturun:

import UIKit

// MARK: Retrieve TextField first responder for keyboard
extension UIResponder {

  private static weak var currentResponder: UIResponder?

  static var currentFirstResponder: UIResponder? {
    currentResponder = nil
    UIApplication.shared.sendAction(#selector(UIResponder.findFirstResponder),
                                    to: nil, from: nil, for: nil)
    return currentResponder
  }

  @objc private func findFirstResponder(_ sender: Any) {
    UIResponder.currentResponder = self
  }

  // Frame of the superview
  var globalFrame: CGRect? {
    guard let view = self as? UIView else { return nil }
    return view.superview?.convert(view.frame, to: nil)
  }
}

Artık bir klavyenin TextField'ı gizlemesini önlemek için Combine kullanarak KeyboardModifier'ı oluşturabilirsiniz:

import SwiftUI
import Combine

// MARK: Keyboard show/hide VStack offset modifier
struct KeyboardModifier: ViewModifier {

  @State var offset: CGFloat = .zero
  @State var subscription = Set<AnyCancellable>()

  func body(content: Content) -> some View {
    GeometryReader { geometry in
      content
        .padding(.bottom, self.offset)
        .animation(.spring(response: 0.4, dampingFraction: 0.5, blendDuration: 1))
        .onAppear {

          NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)
            .handleEvents(receiveOutput: { _ in self.offset = 0 })
            .sink { _ in }
            .store(in: &self.subscription)

          NotificationCenter.default.publisher(for: UIResponder.keyboardWillChangeFrameNotification)
            .map(\.userInfo)
            .compactMap { ($0?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.size.height }
            .sink(receiveValue: { keyboardHeight in
              let keyboardTop = geometry.frame(in: .global).height - keyboardHeight
              let textFieldBottom = UIResponder.currentFirstResponder?.globalFrame?.maxY ?? 0
              self.offset = max(0, textFieldBottom - keyboardTop * 2 - geometry.safeAreaInsets.bottom) })
        .store(in: &self.subscription) }
        .onDisappear {
          // Dismiss keyboard
          UIApplication.shared.windows
            .first { $0.isKeyWindow }?
            .endEditing(true)

          self.subscription.removeAll() }
    }
  }
}

1

İOS 14 (beta 4) için oldukça basit çalışıyor:

var body: some View {
    VStack {
        TextField(...)
    }
    .padding(.bottom, 0)
}

Ve görünümün boyutu klavyenin üst kısmına göre ayarlanır. Çerçeve (.maxHeight: ...) vb. İle kesinlikle mümkün olan daha fazla iyileştirme vardır. Bunu anlayacaksınız.

Ne yazık ki iPad'deki hareketli klavye, taşındığında hala sorunlara neden oluyor. Ancak yukarıda belirtilen çözümler de olacaktır ve hala beta, umarım çözerler.

Sonunda Thx Apple!


Bu hiç çalışmıyor (14.1). Fikir nedir?
Burgler-dev

0

Benim görüşüm:

struct AddContactView: View {
    
    @Environment(\.presentationMode) var presentationMode : Binding<PresentationMode>
    
    @ObservedObject var addContactVM = AddContactVM()
    
    @State private var offsetValue: CGFloat = 0.0
    
    @State var firstName : String
    @State var lastName : String
    @State var sipAddress : String
    @State var phoneNumber : String
    @State var emailID : String
    
  
    var body: some View {
        
        
        VStack{
            
            Header(title: StringConstants.ADD_CONTACT) {
                
                self.presentationMode.wrappedValue.dismiss()
            }
            
           ScrollView(Axis.Set.vertical, showsIndicators: false){
            
            Image("contactAvatar")
                .padding(.top, 80)
                .padding(.bottom, 100)
                //.padding(.vertical, 100)
                //.frame(width: 60,height : 60).aspectRatio(1, contentMode: .fit)
            
            VStack(alignment: .center, spacing: 0) {
                
                
                TextFieldBorder(placeHolder: StringConstants.FIRST_NAME, currentText: firstName, imageName: nil)
                
                TextFieldBorder(placeHolder: StringConstants.LAST_NAME, currentText: lastName, imageName: nil)
                
                TextFieldBorder(placeHolder: StringConstants.SIP_ADDRESS, currentText: sipAddress, imageName: "sipPhone")
                TextFieldBorder(placeHolder: StringConstants.PHONE_NUMBER, currentText: phoneNumber, imageName: "phoneIcon")
                TextFieldBorder(placeHolder: StringConstants.EMAILID, currentText: emailID, imageName: "email")
                

            }
            
           Spacer()
            
        }
        .padding(.horizontal, 20)
        
            
        }
        .padding(.bottom, self.addContactVM.bottomPadding)
        .onAppear {
            
            NotificationCenter.default.addObserver(self.addContactVM, selector: #selector(self.addContactVM.keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
            
             NotificationCenter.default.addObserver(self.addContactVM, selector: #selector(self.addContactVM.keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
        }
        
    }
}

Sanal makinem:

class AddContactVM : ObservableObject{
    
    @Published var contact : Contact = Contact(id: "", firstName: "", lastName: "", phoneNumbers: [], isAvatarAvailable: false, avatar: nil, emailID: "")
    
    @Published var bottomPadding : CGFloat = 0.0
    
    @objc  func keyboardWillShow(_ notification : Notification){
        
        if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
            let keyboardRectangle = keyboardFrame.cgRectValue
            let keyboardHeight = keyboardRectangle.height
            self.bottomPadding = keyboardHeight
        }
        
    }
    
    @objc  func keyboardWillHide(_ notification : Notification){
        
        
        self.bottomPadding = 0.0
        
    }
    
}

Temel olarak, klavye yüksekliğine göre alt dolguyu yönetme.


-3

Buna başardığım en zarif cevap rraphael'in çözümüne benziyor. Klavye olaylarını dinlemek için bir sınıf oluşturun. Yine de dolguyu değiştirmek için klavye boyutunu kullanmak yerine, klavye boyutunun negatif bir değerini döndürün ve en dıştaki kapların ofsetini ayarlamak için .offset (y :) değiştiricisini kullanın. Yeterince iyi canlandırır ve her görünümde çalışır.


Bunu nasıl canlandırdın? Bende var .offset(y: withAnimation { -keyboard.currentHeight }), ancak içerik canlandırmak yerine atlıyor.
jjatie

Birkaç betas önce bu kodla karıştırdım, ancak daha önceki yorumumda, çalışma zamanı sırasında bir vstack ofsetini değiştirmek gerekli olan tek şeydi, SwiftUI değişikliği sizin için canlandıracaktı.
pcallycat
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.