ย้าย TextField ขึ้นเมื่อแป้นพิมพ์ปรากฏใน SwiftUI


100

ฉันมีเจ็ดภายในหลักของฉัน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"))
            }
    }
}

เอาท์พุต:

เอาต์พุต



1
@PrashantTukadiya ขอบคุณสำหรับการตอบกลับอย่างรวดเร็ว ฉันได้เพิ่ม TextField ใน Scrollview แล้ว แต่ยังคงประสบปัญหาเดียวกัน
Hitesh Surani

1
@DimaPaliychuk นี้จะไม่ทำงาน มันคือ SwiftUI
Prashant Tukadiya

20
การแสดงแป้นพิมพ์และการบดบังเนื้อหาบนหน้าจอนั้นเกิดขึ้นตั้งแต่อะไรแอป Objective C iPhone ตัวแรก? นี่คือปัญหาที่ได้รับการแก้ไขอย่างต่อเนื่อง ฉันรู้สึกผิดหวังอย่างหนึ่งที่ Apple ไม่ได้จัดการกับ SwiftUi ฉันรู้ว่าความคิดเห็นนี้ไม่เป็นประโยชน์กับทุกคน แต่ฉันต้องการยกระดับปัญหานี้ว่าเราควรกดดันให้ Apple เสนอวิธีแก้ปัญหาและไม่ต้องพึ่งพาชุมชนในการจัดหาปัญหาที่พบบ่อยที่สุดนี้
P. Ent

1
มีบทความที่ดีมากโดย Vadim vadimbulavin.com/…
Sudara

คำตอบ:


64

รหัสอัปเดตสำหรับ Xcode เบต้า 7

คุณไม่จำเป็นต้องมีช่องว่างภายใน ScrollViews หรือรายการเพื่อให้บรรลุเป้าหมายนี้ แม้ว่าโซลูชันนี้จะเล่นได้ดีกับพวกเขาด้วย ฉันรวมสองตัวอย่างไว้ที่นี่

อันแรกจะย้ายtextField ทั้งหมดขึ้นหากแป้นพิมพ์ปรากฏขึ้นสำหรับแป้นพิมพ์ใด ๆ แต่ถ้าจำเป็นเท่านั้น หากแป้นพิมพ์ไม่ซ่อนช่องข้อความก็จะไม่ขยับ

ในตัวอย่างที่สองมุมมองจะเคลื่อนไหวเพียงพอเพียงเพื่อหลีกเลี่ยงการซ่อนช่องข้อความที่ใช้งานอยู่

ทั้งสองตัวอย่างใช้รหัสทั่วไปเดียวกันที่พบในตอนท้าย: GeometryGetterและKeyboardGuardian

ตัวอย่างแรก (แสดงฟิลด์ข้อความทั้งหมด)

เมื่อเปิดแป้นพิมพ์ช่องข้อความทั้ง 3 ช่องจะเลื่อนขึ้นมากพอที่จะทำให้มองเห็นได้ทั้งหมด

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

}

ตัวอย่างที่สอง (แสดงเฉพาะฟิลด์ที่ใช้งานอยู่)

เมื่อคลิกแต่ละช่องข้อความมุมมองจะถูกเลื่อนขึ้นเพียงพอที่จะทำให้มองเห็นช่องข้อความที่คลิกได้

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

}

เรขาคณิต

นี่คือมุมมองที่ดูดซับขนาดและตำแหน่งของมุมมองระดับบนสุด เพื่อให้บรรลุเป้าหมายนั้นเรียกว่าภายในตัวปรับแต่ง. background นี่เป็นตัวปรับแต่งที่ทรงพลังมากไม่ใช่แค่วิธีตกแต่งพื้นหลังของมุมมอง เมื่อส่งมุมมองไปยัง. background (MyView ()) MyView จะได้รับมุมมองที่แก้ไขเป็นพาเรนต์ การใช้ GeometryReader เป็นสิ่งที่ทำให้มุมมองทราบรูปทรงเรขาคณิตของพาเรนต์

ตัวอย่างเช่นText("hello").background(GeometryGetter(rect: $bounds))จะเติมเต็มขอบเขตของตัวแปรด้วยขนาดและตำแหน่งของมุมมองข้อความและใช้พื้นที่พิกัดส่วนกลาง

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

อัปเดตฉันได้เพิ่ม DispatchQueue.main.async เพื่อหลีกเลี่ยงความเป็นไปได้ในการแก้ไขสถานะของมุมมองในขณะที่กำลังแสดงผล ***

KeyboardGuardian

วัตถุประสงค์ของ KeyboardGuardian คือการติดตามการแสดงแป้นพิมพ์ / ซ่อนเหตุการณ์และคำนวณว่าจะต้องเปลี่ยนพื้นที่มุมมองเท่าใด

อัปเดต: ฉันแก้ไข KeyboardGuardian เพื่อรีเฟรชสไลด์เมื่อผู้ใช้แท็บจากฟิลด์หนึ่งไปอีกฟิลด์หนึ่ง

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
เป็นไปได้ไหมที่จะแนบGeometryGetterเป็นตัวปรับมุมมองมากกว่าพื้นหลังโดยทำให้เป็นไปตามViewModifierโปรโตคอล?
Sudara

2
เป็นไปได้ แต่กำไรคืออะไร? คุณจะได้รับการติดมันเช่นนี้แทน.modifier(GeometryGetter(rect: $kGuardian.rects[1])) .background(GeometryGetter(rect: $kGuardian.rects[1]))ความแตกต่างไม่มาก (น้อยกว่าเพียง 2 อักขระ)
kontiki

3
ในบางสถานการณ์คุณอาจได้รับ SIGNAL ABORT จากโปรแกรมภายใน GeometryGetter เมื่อกำหนดรูปสี่เหลี่ยมผืนผ้าใหม่หากคุณกำลังออกจากหน้าจอนี้ หากสิ่งนี้เกิดขึ้นกับคุณเพียงแค่เพิ่มโค้ดเพื่อตรวจสอบว่าขนาดของรูปทรงเรขาคณิตนั้นมากกว่าศูนย์ (geometry.size.width> 0 && geometry.size.height> 0) ก่อนกำหนดค่าให้ self.rect
Julio Bailon

1
@JulioBailon ฉันไม่รู้ว่าทำไม แต่การย้ายgeometry.frameออกจากความDispatchQueue.main.asyncช่วยเหลือด้วย SIGNAL ABORT ตอนนี้จะทดสอบวิธีแก้ปัญหาของคุณ อัปเดต: if geometry.size.width > 0 && geometry.size.height > 0ก่อนมอบหมายความself.rectช่วยเหลือ
Roman Vasilyev

2
สิ่งนี้แบ่งให้ฉันเช่นกันใน self.rect = geometry.frame (ใน: .global) รับ SIGNAL ABORT และลองใช้วิธีแก้ไขปัญหาที่เสนอทั้งหมดเพื่อแก้ไขข้อผิดพลาดนี้
Marwan Roushdy

55

เพื่อสร้างโซลูชันของ @rraphael ฉันได้แปลงให้ใช้งานได้โดยการสนับสนุน xcode11 swiftUI ในปัจจุบัน

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

การใช้งาน:

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

การเผยแพร่currentHeightจะทริกเกอร์การเรนเดอร์ UI อีกครั้งและเลื่อน TextField ของคุณขึ้นเมื่อแป้นพิมพ์แสดงขึ้นและกลับลงเมื่อปิด อย่างไรก็ตามฉันไม่ได้ใช้ ScrollView


6
ฉันชอบคำตอบนี้สำหรับความเรียบง่าย ฉันเพิ่ม.animation(.easeOut(duration: 0.16))เพื่อพยายามจับคู่ความเร็วของแป้นพิมพ์ที่เลื่อนขึ้น
Mark Moeykens

เหตุใดคุณจึงตั้งค่าความสูงสูงสุด 340 สำหรับแป้นพิมพ์
Daniel Ryan

1
@DanielRyan บางครั้งความสูงของแป้นพิมพ์ก็ส่งคืนค่าที่ไม่ถูกต้องในโปรแกรมจำลอง ฉันไม่สามารถหาวิธีที่จะยุติปัญหาได้ในขณะนี้
Michael Neas

1
ฉันไม่เห็นปัญหานั้นด้วยตัวเอง อาจได้รับการแก้ไขในเวอร์ชันล่าสุด ฉันไม่ต้องการล็อคขนาดในกรณีที่มีแป้นพิมพ์ที่ใหญ่กว่า (หรือจะเป็น)
Daniel Ryan

1
keyboardFrameEndUserInfoKeyคุณอาจจะลองด้วย นั่นควรถือเป็นเฟรมสุดท้ายสำหรับแป้นพิมพ์
Mathias Claassen

50

ฉันลองใช้วิธีแก้ปัญหาที่นำเสนอมากมายและแม้ว่าจะใช้งานได้ในกรณีส่วนใหญ่ แต่ฉันก็มีปัญหาบางอย่าง - ส่วนใหญ่เป็นพื้นที่ปลอดภัย (ฉันมีแบบฟอร์มอยู่ในแท็บของ TabView)

ฉันลงเอยด้วยการรวมโซลูชันที่แตกต่างกันเล็กน้อยและใช้ GeometryReader เพื่อให้ได้มุมมองด้านล่างที่ปลอดภัยของมุมมองที่เฉพาะเจาะจงและใช้ในการคำนวณของช่องว่างภายใน:

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

การใช้งาน:

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

7
ว้าวนี่เป็นเวอร์ชัน SwiftUI ที่มากที่สุดพร้อม GeometryReader และ ViewModifier รักมัน.
Mateusz

3
สิ่งนี้มีประโยชน์และสวยงามมาก ขอบคุณมากสำหรับการเขียนสิ่งนี้
Danilo Campos

2
ฉันเห็นมุมมองสีขาวว่างเปล่าเล็ก ๆ บนแป้นพิมพ์ของฉัน มุมมองนี้คือมุมมอง GeometryReader ฉันได้รับการยืนยันโดยการเปลี่ยนสีพื้นหลัง มีความคิดว่าทำไม GeometryReader จึงแสดงระหว่างมุมมองและแป้นพิมพ์จริงของฉัน
user832

6
ฉันได้รับข้อผิดพลาดThread 1: signal SIGABRTออนไลน์rect.height - geometry.safeAreaInsets.bottomเมื่อฉันไปที่มุมมองด้วยแป้นพิมพ์เป็นครั้งที่สองและคลิกที่ไฟล์TextField. ไม่สำคัญว่าฉันจะคลิกTextFieldครั้งแรกหรือไม่ แอปยังคงขัดข้อง
JLively

2
ในที่สุดสิ่งที่ได้ผล! ทำไมแอปเปิ้ลถึงทำไม่ได้สำหรับเรานี่แหล่ะ !!
Dave Kozikowski

35

ฉันสร้างมุมมองที่สามารถห่อมุมมองอื่น ๆ เพื่อย่อขนาดเมื่อแป้นพิมพ์ปรากฏขึ้น

มันค่อนข้างง่าย เราสร้างผู้เผยแพร่สำหรับแป้นพิมพ์แสดง / onReceiveซ่อนเหตุการณ์และแล้วการสมัครสมาชิกกับพวกเขาโดยใช้ เราใช้ผลลัพธ์ของสิ่งนั้นเพื่อสร้างสี่เหลี่ยมผืนผ้าขนาดแป้นพิมพ์ด้านหลังแป้นพิมพ์

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

จากนั้นคุณสามารถใช้มุมมองดังนี้:

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

หากต้องการย้ายเนื้อหาของมุมมองขึ้นแทนที่จะviewย่อขนาดสามารถเพิ่มช่องว่างภายในหรือออฟเซ็ตได้แทนที่จะใส่ลงใน VStack ด้วยสี่เหลี่ยมผืนผ้า


6
ฉันคิดว่านี่คือคำตอบที่ถูกต้อง ฉันปรับแต่งเล็กน้อย: แทนที่จะเป็นสี่เหลี่ยมผืนผ้าฉันแค่ปรับเปลี่ยนช่องว่างภายในself.viewและใช้งานได้ดี ไม่มีปัญหาเลยกับแอนิเมชั่น
Tae

5
ขอบคุณ! ทำงานได้อย่างสมบูรณ์ ดังที่ @Taed กล่าวว่าควรใช้วิธีการเติมเต็ม ผลสุดท้ายจะเป็นvar body: some View { VStack { view .padding(.bottom, keyboardHeight) .animation(.default) } .onReceive(showPublisher.merge(with: hidePublisher)) { (height) in self.keyboardHeight = height } }
fdelafuente

1
แม้จะมีคะแนนโหวตน้อยกว่า แต่ก็เป็นการตอบกลับที่รวดเร็วที่สุด และวิธีการก่อนหน้านี้โดยใช้ AnyView แบ่งความช่วยเหลือการเร่งความเร็วโลหะ
Nelson Cardaci

4
เป็นวิธีแก้ปัญหาที่ยอดเยี่ยม แต่ปัญหาหลักที่นี่คือคุณไม่สามารถเลื่อนมุมมองขึ้นได้ก็ต่อเมื่อแป้นพิมพ์ซ่อนช่องข้อความที่คุณกำลังแก้ไข ฉันหมายถึง: ถ้าคุณมีแบบฟอร์มที่มีช่องข้อความหลายช่องและคุณเริ่มแก้ไขอันแรกที่ด้านบนคุณอาจไม่ต้องการให้มันเลื่อนขึ้นเพราะมันจะย้ายออกจากหน้าจอ
superpuccio

ฉันชอบคำตอบนี้มาก แต่ก็เหมือนกับคำตอบอื่น ๆ ทั้งหมดมันใช้ไม่ได้ถ้ามุมมองของคุณอยู่ใน TabBar หรือมุมมองไม่ได้ล้างที่ด้านล่างของหน้าจอ
Ben Patch

25

ฉันได้สร้างตัวปรับแต่งมุมมองที่ใช้งานง่ายจริงๆ

เพิ่มไฟล์ Swift ด้วยโค้ดด้านล่างและเพิ่มตัวปรับแต่งนี้ในมุมมองของคุณ:

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


ดี. ขอบคุณสำหรับการแบ่งปัน.
ทศวรรษ

2
จะดีมากถ้ามันจะชดเชยถ้าจำเป็นเท่านั้น (เช่นอย่าเลื่อนถ้าแป้นพิมพ์ไม่ครอบคลุมองค์ประกอบอินพุต) ยินดีที่ได้ ...
ทศวรรษ

มันใช้งานได้ดีขอบคุณ การใช้งานที่สะอาดมากเช่นกันและสำหรับฉันเลื่อนเฉพาะเมื่อจำเป็นเท่านั้น
Joshua

สุดยอด! ทำไมคุณไม่ระบุสิ่งนี้ใน Github หรือที่อื่น ๆ :) หรือคุณสามารถแนะนำสิ่งนี้ได้ที่github.com/hackiftekhar/IQKeyboardManagerเนื่องจากยังไม่มีการสนับสนุน SwiftUI เต็มรูปแบบ
Schnodderbalken

จะเล่นได้ไม่ดีกับการเปลี่ยนแปลงการวางแนวและจะหักล้างไม่ว่าจะจำเป็นหรือไม่ก็ตาม
GrandSteph

16

หรือคุณสามารถใช้IQKeyBoardManagerSwift

และสามารถเลือกที่จะเพิ่มสิ่งนี้ไปยังผู้ร่วมประชุมแอปของคุณเพื่อซ่อนแถบเครื่องมือและเปิดใช้งานการซ่อนแป้นพิมพ์เมื่อคลิกที่มุมมองอื่น ๆ จากนั้นแป้นพิมพ์

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

นี่เป็นวิธีที่ (ไม่คาดคิด) สำหรับฉันเช่นกัน ของแข็ง
HelloTimo

กรอบนี้ทำงานได้ดีกว่าที่คาดไว้ ขอบคุณสำหรับการแชร์!
Richard Poutier

1
ทำงานได้ดีสำหรับฉันบน SwiftUI - ขอบคุณ @DominatorVbN - ฉันใช้โหมดแนวนอนของ iPad ฉันต้องเพิ่มIQKeyboardManager.shared.keyboardDistanceFromTextFieldเป็น 40 เพื่อให้ได้ช่องว่างที่สะดวกสบาย
Richard Groves

ยังต้องตั้งค่าIQKeyboardManager.shared.enable = trueเพื่อป้องกันไม่ให้แป้นพิมพ์ซ่อนช่องข้อความของฉัน ไม่ว่าในกรณีใดนี่เป็นทางออกที่ดีที่สุด ฉันมี 4 ฟิลด์ที่จัดเรียงในแนวตั้งและวิธีแก้ปัญหาอื่น ๆ จะใช้ได้กับฟิลด์ล่างสุดของฉัน แต่จะผลักดันให้มุมมองด้านบนสุด
MisterEd

12

คุณต้องเพิ่มScrollViewและกำหนดขนาดของแป้นพิมพ์ด้านล่างเพื่อให้เนื้อหาสามารถเลื่อนได้เมื่อแป้นพิมพ์ปรากฏขึ้น

ในการรับขนาดแป้นพิมพ์คุณจะต้องใช้NotificationCenterเพื่อลงทะเบียนเหตุการณ์คีย์บอร์ด คุณสามารถใช้คลาสที่กำหนดเองเพื่อดำเนินการดังต่อไปนี้:

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

ความ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การปรับขนาดมุมมองแบบเลื่อน


ดูเหมือนว่าคุณจะมีประสบการณ์ในเรื่อง bindableobject ฉันไม่สามารถทำงานได้ตามที่ฉันต้องการ คงจะดีไม่น้อยหากคุณสามารถมองข้าม: stackoverflow.com/questions/56500147/…
SwiftiSwift

ทำไมคุณไม่ใช้ @ObjectBinding
SwiftiSwift

3
เมื่อBindableObjectเลิกใช้งานแล้วสิ่งนี้จะใช้งานไม่ได้อีกต่อไปน่าเสียดาย
LinusGeffarth

2
@LinusGeffarth สำหรับสิ่งที่คุ้มค่าBindableObjectถูกเปลี่ยนชื่อเป็นObservableObjectและdidChangeเพื่อobjectWillChangeที่จะวัตถุอัปเดตมุมมองได้ดี (แม้ว่าฉันจะทดสอบโดยใช้ไฟล์@ObservedObjectแทน@State)
SeizeTheDay

สวัสดีโซลูชันนี้กำลังเลื่อนเนื้อหา แต่จะแสดงพื้นที่สีขาวเหนือแป้นพิมพ์ซึ่งซ่อนครึ่งหนึ่งของฟิลด์ข้อความ โปรดแจ้งให้เราทราบว่าเราจะลบพื้นที่สีขาวได้อย่างไร
Shahbaz Sajjad

12

Xcode 12 - รหัสบรรทัดเดียว

เพิ่มตัวปรับแต่งนี้ในไฟล์ TextField

.ignoresSafeArea(.keyboard, edges: .bottom)

การสาธิต

Apple ได้เพิ่มแป้นพิมพ์เป็นภูมิภาคสำหรับพื้นที่ปลอดภัยเพื่อให้คุณสามารถใช้แป้นพิมพ์เพื่อเคลื่อนย้ายส่วนใดก็ได้Viewด้วยแป้นพิมพ์เหมือนกับภูมิภาคอื่น ๆ


มันใช้งานได้กับ TextField แต่ TextEditor ล่ะ?
АндрейПервушин

ใช้งานได้กับAny Viewรวมถึงไฟล์TextEditor.
Mojtaba Hosseini

3
เพิ่งทดสอบ Xcode beta 6, TextEditor ไม่ทำงาน
АндрейПервушин

1
คุณสามารถถามคำถามและเชื่อมโยงได้ที่นี่ ดังนั้นฉันสามารถดูรหัสที่ทำซ้ำได้ของคุณและดูว่าฉันสามารถช่วยได้ไหม :) @kyrers
Mojtaba Hosseini

2
ฉันคิดออกด้วยตัวเอง เพิ่ม.ignoresSafeArea(.keyboard)ในมุมมองของคุณ
leonboe1

6

ฉันตรวจสอบและปรับโครงสร้างโซลูชันที่มีอยู่เป็นแพ็คเกจ SPM ที่มีประโยชน์ซึ่งมี.keyboardAware()ตัวปรับแต่ง:

KeyboardAwareSwiftUI

ตัวอย่าง:

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

    }
}

ที่มา:

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

ฉันเห็นเพียงครึ่งหนึ่งของความสูงของ textview คุณรู้วิธีแก้ปัญหานี้หรือไม่?
Matrosov Alexander

ดี. นี่ช่วยประหยัดเวลาของฉัน ก่อนที่จะใช้สิ่งนี้เราทราบว่าช่องว่างด้านล่างของมุมมองตัวปรับเปลี่ยนนี้จัดการ
Brownsoo Han

5

ฉันใช้คำตอบของเบนจามินคินเดิลเป็นจุดเริ่มต้น แต่ฉันมีปัญหาบางอย่างที่ฉันต้องการแก้ไข

  1. คำตอบส่วนใหญ่ไม่ได้เกี่ยวข้องกับแป้นพิมพ์ที่เปลี่ยนกรอบดังนั้นจะแตกหากผู้ใช้หมุนอุปกรณ์ด้วยแป้นพิมพ์บนหน้าจอ การเพิ่มkeyboardWillChangeFrameNotificationในรายการที่อยู่การแจ้งเตือนที่ดำเนินการนี้
  2. ฉันไม่ต้องการให้ผู้เผยแพร่โฆษณาหลายรายมีการปิดแผนที่ที่เหมือนกัน แต่แตกต่างกันดังนั้นฉันจึงผูกมัดการแจ้งเตือนแป้นพิมพ์ทั้งสามไว้ในผู้เผยแพร่รายเดียว เป็นห่วงโซ่ยาว แต่แต่ละขั้นตอนค่อนข้างตรงไปตรงมา
  3. ฉันจัดเตรียมinitฟังก์ชันที่ยอมรับ a @ViewBuilderเพื่อให้คุณสามารถใช้KeyboardHostมุมมองเหมือนกับมุมมองอื่น ๆ และส่งผ่านเนื้อหาของคุณในการปิดท้ายแทนที่จะส่งผ่านมุมมองเนื้อหาเป็นพารามิเตอร์ไปยังinitและก็ผ่านเนื้อหาของคุณในการปิดท้ายเมื่อเทียบกับการส่งผ่านมุมมองที่เนื้อหาเป็นพารามิเตอร์ไป
  4. ตามที่ Tae และ fdelafuente แนะนำในความคิดเห็นฉันได้เปลี่ยนไฟล์ Rectangleสำหรับปรับช่องว่างด้านล่าง
  5. แทนที่จะใช้สตริง "UIKeyboardFrameEndUserInfoKey" แบบฮาร์ดโค้ดฉันต้องการใช้สตริงที่ให้มาในUIWindowรูปแบบUIWindow.keyboardFrameEndUserInfoKey.

ฉันดึงสิ่งเหล่านี้เข้าด้วยกัน:

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


วิธีนี้ไม่ได้ผลมันจะเพิ่มKeyboardความสูง
GSerjo

คุณช่วยอธิบายปัญหาที่คุณพบ @GSerjo ได้ไหม ฉันใช้รหัสนี้ในแอปของฉันและมันก็ใช้ได้ดีสำหรับฉัน
Timothy Sanders

คุณช่วยเปิดPridictiveใน iOS keyboardได้ไหม Settings-> General-> ->Keyboard Pridictiveในกรณีนี้มันไม่ได้แก้ไขการคำนวณและเพิ่มช่องว่างให้กับแป้นพิมพ์
GSerjo

@GSerjo: ฉันเปิดใช้งาน Predictive text บน iPad Touch (รุ่นที่ 7) ที่ใช้ iOS 13.1 เบต้า เพิ่มช่องว่างสำหรับความสูงของแถวการคาดการณ์อย่างถูกต้อง (โปรดทราบว่าฉันไม่ได้ปรับความสูงของแป้นพิมพ์ที่นี่ฉันกำลังเพิ่มช่องว่างของมุมมองเอง) ลองสลับในแผนที่ดีบักที่แสดงความคิดเห็นและเล่นกับค่าที่คุณได้รับสำหรับการคาดการณ์ แป้นพิมพ์ ฉันจะโพสต์บันทึกในความคิดเห็นอื่น
Timothy Sanders

กับ "การแก้จุดบกพร่อง" แผนที่ uncommented keyboardHeightคุณสามารถเห็นคุณค่าที่ถูกมอบหมายให้ บน iPod Touch ของฉัน (ในแนวตั้ง) แป้นพิมพ์ที่มีการคาดคะเนคือ 254 คะแนน ถ้าไม่มีมันคือ 216 คะแนน ฉันสามารถปิดการคาดเดาได้ด้วยแป้นพิมพ์บนหน้าจอและการอัปเดตช่องว่างภายในอย่างถูกต้อง การเพิ่มแป้นพิมพ์พร้อมระบบช่วยสะกดคำ: Received UIKeyboardWillChangeFrameNotification with height 254.0 Received UIKeyboardWillShowNotification with height 254.0 เมื่อฉันปิดข้อความคาดเดา:Received UIKeyboardWillChangeFrameNotification with height 216.0
Timothy Sanders

4

สิ่งนี้ดัดแปลงมาจากสิ่งที่ @kontiki สร้างขึ้น ฉันเปิดใช้งานในแอปภายใต้เบต้า 8 / เมล็ดพันธุ์ GM โดยที่ฟิลด์ที่ต้องเลื่อนเป็นส่วนหนึ่งของแบบฟอร์มภายใน NavigationView นี่คือ 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
        }
    }
}

จากนั้นฉันใช้ enum เพื่อติดตามสล็อตในอาร์เรย์ rects และจำนวนทั้งหมด:

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

KeyboardSlots.count.rawValueคือความจุอาร์เรย์ที่จำเป็น อื่น ๆ เป็น rawValue ให้ดัชนีที่เหมาะสมที่คุณจะใช้สำหรับการเรียก. background (GeometryGetter)

ด้วยการตั้งค่าดังกล่าวมุมมองจะไปที่ KeyboardGuardian ด้วยสิ่งนี้:

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

การเคลื่อนไหวที่แท้จริงเป็นดังนี้:

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

แนบกับมุมมอง ในกรณีของฉันมันเชื่อมต่อกับ NavigationView ทั้งหมดดังนั้นการประกอบทั้งหมดจึงเลื่อนขึ้นเมื่อแป้นพิมพ์ปรากฏขึ้น

ฉันยังไม่ได้แก้ปัญหาในการรับแถบเครื่องมือเสร็จสิ้นหรือปุ่มย้อนกลับบนแป้นพิมพ์ทศนิยมด้วย SwiftUI ฉันจึงใช้สิ่งนี้เพื่อซ่อนมันในการแตะที่อื่นแทน:

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

คุณแนบเข้ากับมุมมองเป็น

.modifier(DismissingKeyboard())

มุมมองบางอย่าง (เช่นตัวเลือก) ไม่ชอบการแนบนั้นดังนั้นคุณอาจต้องมีความละเอียดในการแนบตัวปรับแต่งแทนที่จะตบที่มุมมองด้านนอกสุด

ขอบคุณมากที่ @kontiki สำหรับการทำงานหนัก คุณจะยังคงต้องใช้ GeometryGetter ของเขาด้านบน (ไม่ใช่ฉันไม่ได้ทำงานเพื่อแปลงเป็นค่ากำหนดเช่นกัน) ตามที่เขาแสดงในตัวอย่าง


1
สำหรับบุคคลที่โหวตลดลง: ทำไม? ฉันพยายามเพิ่มสิ่งที่เป็นประโยชน์ดังนั้นฉันอยากทราบว่าในมุมมองของคุณฉันผิดพลาดอย่างไร
Feldur

4

วิธีแก้ปัญหาสองสามข้อข้างต้นมีปัญหาบางประการและไม่จำเป็นต้องเป็นแนวทางที่ "สะอาดที่สุด" ด้วยเหตุนี้ฉันจึงได้ปรับเปลี่ยนบางสิ่งสำหรับการใช้งานด้านล่าง

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

ฉันชอบแนวทางที่สะอาดกว่าและย้ายความรับผิดชอบไปยังมุมมองที่สร้างขึ้น (ไม่ใช่ตัวปรับแต่ง) ในการชดเชยเนื้อหา แต่ดูเหมือนว่าฉันจะไม่สามารถให้ผู้เผยแพร่ทริกเกอร์ได้อย่างถูกต้องเมื่อย้ายโค้ดออฟเซ็ตไปที่มุมมอง ...

นอกจากนี้โปรดทราบว่าผู้เผยแพร่โฆษณาต้องถูกใช้ในอินสแตนซ์นี้เนื่องจากfinal classปัจจุบันเกิดข้อผิดพลาดที่ไม่ทราบสาเหตุ (แม้ว่าจะเป็นไปตามข้อกำหนดของอินเทอร์เฟซก็ตาม) และ ScrollView โดยรวมเป็นแนวทางที่ดีที่สุดเมื่อใช้โค้ดออฟเซ็ต


ทางออกที่ดีมากขอแนะนำ! ฉันเพิ่ม Bool เพื่อระบุว่าแป้นพิมพ์กำลังทำงานอยู่หรือไม่
Peanutsmasher

4

คำตอบเหล่านี้จำนวนมากดูเหมือนจะป่องจริงๆ หากคุณใช้ SwiftUI คุณอาจใช้ประโยชน์จาก Combine ได้เช่นกัน

สร้างKeyboardResponderตามที่แสดงด้านล่างจากนั้นคุณสามารถใช้ตามที่แสดงไว้ก่อนหน้านี้

อัปเดตสำหรับ iOS 14

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

ฉันเพิ่ม .animation (.easeIn) เพื่อให้ตรงกับภาพเคลื่อนไหวที่แป้นพิมพ์ปรากฏ
Michiel Pelt

(สำหรับ iOS 13 ไปที่ประวัติของคำตอบนี้)
Loris Foe

สวัสดี, .assign (ถึง: \ .keyboardHeight) ให้ข้อผิดพลาดนี้ "ไม่สามารถอนุมานประเภทเส้นทางคีย์จากบริบทได้โปรดพิจารณาระบุประเภทรูทอย่างชัดเจน" โปรดแจ้งวิธีแก้ปัญหาที่เหมาะสมและสะอาดสำหรับทั้ง ios 13 และ ios 14
Shahbaz Sajjad

3

ฉันไม่แน่ใจว่า API การเปลี่ยนแปลง / แอนิเมชั่นสำหรับ SwiftUI นั้นสมบูรณ์หรือไม่ แต่คุณสามารถใช้CGAffineTransformกับไฟล์.transformEffect

สร้างวัตถุแป้นพิมพ์ที่สังเกตได้ด้วยคุณสมบัติที่เผยแพร่ดังนี้:

    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
    }

}

จากนั้นคุณสามารถใช้คุณสมบัตินั้นเพื่อจัดเรียงมุมมองของคุณใหม่ดังนี้:

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

หรือง่ายกว่า

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

ฉันชอบคำตอบนี้ แต่ฉันไม่ค่อยแน่ใจว่า "ScreenSize.portrait" มาจากไหน
Misha Stone

สวัสดี @MishaStone ขอบคุณที่เลือกแนวทางของฉัน ScreenSize.portrait เป็นคลาสที่ฉันสร้างขึ้นเพื่อให้ได้การวัดฐานหน้าจอตามการวางแนวและเปอร์เซ็นต์ .... แต่คุณสามารถแทนที่ด้วยค่าใดก็ได้ที่คุณต้องการสำหรับการแปลของคุณ
blacktiago

3

Xcode 12 beta 4 เพิ่มตัวปรับมุมมองใหม่ignoresSafeAreaที่คุณสามารถใช้เพื่อหลีกเลี่ยงแป้นพิมพ์ได้แล้ว

.ignoresSafeArea([], edges: [])

ซึ่งจะหลีกเลี่ยงแป้นพิมพ์และขอบพื้นที่ปลอดภัยทั้งหมด คุณสามารถตั้งค่าพารามิเตอร์แรกเป็น.keyboardถ้าคุณไม่ต้องการหลีกเลี่ยง มีความแปลกประหลาดอยู่บ้างอย่างน้อยก็ในการตั้งค่าลำดับชั้นมุมมองของฉัน แต่ดูเหมือนว่านี่เป็นวิธีที่ Apple ต้องการให้เราหลีกเลี่ยงแป้นพิมพ์


2

คัดลอกคำตอบจากที่นี่: TextField อยู่ด้านบนแป้นพิมพ์เสมอด้วย SwiftUI

ฉันได้ลองใช้วิธีต่างๆแล้ว แต่ไม่มีวิธีใดที่ได้ผลสำหรับฉัน ด้านล่างนี้เป็นอุปกรณ์เดียวที่ใช้งานได้กับอุปกรณ์ต่างๆ

เพิ่มนามสกุลนี้ในไฟล์:

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

ในมุมมองของคุณคุณต้องมีตัวแปรเพื่อผูก offsetValue:

struct IncomeView: View {

  @State private var offsetValue: CGFloat = 0.0

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

2
เพียงแค่ FYI คุณเป็นเจ้าของวัตถุเมื่อโทรNotificationCenter.default.addObserver... คุณต้องจัดเก็บสิ่งเหล่านั้นและลบผู้สังเกตการณ์ในเวลาที่เหมาะสม ...
TheCodingArt

สวัสดี @TheCodingArt ถูกต้องฉันได้ลองทำแบบนี้แล้ว ( oleb.net/blog/2018/01/notificationcenter-removeobserver ) แต่ดูเหมือนจะไม่ได้ผลสำหรับฉันมีความคิดอะไรไหม
เรย์

2

ดังที่ Mark Krenek และ Heiko ได้ชี้ให้เห็น Apple ดูเหมือนว่าจะแก้ไขปัญหานี้ใน Xcode 12 beta 4 เป็นเวลานานสิ่งต่างๆกำลังดำเนินไปอย่างรวดเร็ว ตามบันทึกประจำรุ่นสำหรับ Xcode 12 beta 5 ที่เผยแพร่เมื่อวันที่ 18 สิงหาคม 2020 "Form, List และ TextEditor ไม่ได้ซ่อนเนื้อหาไว้ด้านหลังแป้นพิมพ์อีกต่อไป (66172025)" ฉันเพิ่งดาวน์โหลดและทำการทดสอบอย่างรวดเร็วในโปรแกรมจำลองเบต้า 5 (iPhone SE2) ด้วย Form container ในแอปที่ฉันเริ่มเมื่อไม่กี่วันที่ผ่านมา

ตอนนี้ "ใช้งานได้" สำหรับTextField TextFieldSwiftUI จะจัดเตรียมช่องว่างด้านล่างที่เหมาะสมให้กับแบบฟอร์มการห่อหุ้มโดยอัตโนมัติเพื่อให้มีที่ว่างสำหรับแป้นพิมพ์ และมันจะเลื่อน Form ขึ้นโดยอัตโนมัติเพื่อแสดง TextField เหนือแป้นพิมพ์ ตอนนี้คอนเทนเนอร์ ScrollView ทำงานได้ดีเมื่อคีย์บอร์ดปรากฏขึ้นเช่นกัน

แต่เป็นАндрейПервушинชี้ให้เห็นในความคิดเห็นมีปัญหากับ TextEditor เบต้า 5 และ 6 จะจัดเตรียมช่องว่างด้านล่างที่เหมาะสมให้กับแบบฟอร์มการห่อหุ้มโดยอัตโนมัติเพื่อให้มีที่ว่างสำหรับแป้นพิมพ์ แต่จะไม่เลื่อนแบบฟอร์มขึ้นโดยอัตโนมัติ แป้นพิมพ์จะครอบคลุม TextEditor แตกต่างจาก TextField คือผู้ใช้ต้องเลื่อนแบบฟอร์มเพื่อให้ TextEditor มองเห็นได้ ฉันจะยื่นรายงานข้อบกพร่อง บางที Beta 7 อาจแก้ไขได้ เฉียดฉิว …

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


ฉันเห็นบันทึกประจำรุ่นของ Apple ซึ่งทดสอบกับ beta5 และ beta6 แล้ว TextField ใช้งานได้ TextEditor NOT ฉันพลาดอะไรไป @State var text = "" var body: some View {Form {Section {Text (text) .frame (height: 500)} Section {TextField ("5555", text: $ text) .frame (height: 50)} ส่วน {TextEditor (text: $ text) .frame (height: 120)}}}
АндрейПервушин

2

การใช้งาน:

import SwiftUI

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

รหัส:

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

ลองวิธีแก้ปัญหาของคุณ ... มุมมองถูกเลื่อนไปเพียงครึ่งหนึ่งของฟิลด์ข้อความ ลองวิธีแก้ปัญหาทั้งหมดข้างต้นแล้ว มีปัญหาเดียวกัน กรุณาช่วย!!!
Sona

@Zeona ลองใช้แอปง่ายๆคุณอาจจะทำอะไรที่แตกต่างออกไป ลองลบ '- (UIWindow.keyWindow? .safeAreaInsets.bottom ?? 0)' ถ้าคุณใช้พื้นที่ปลอดภัย
8

ลบ (UIWindow.keyWindow? .safeAreaInsets.bottom ?? 0) จากนั้นฉันได้รับพื้นที่สีขาวเหนือแป้นพิมพ์
Sona

1

การจัดการTabViewของ

ฉันชอบคำตอบของ Benjamin Kindleแต่ไม่รองรับ TabViews นี่คือการปรับรหัสของเขาสำหรับการจัดการ TabViews:

  1. เพิ่มส่วนขยายเพื่อUITabViewจัดเก็บขนาดของ tabView เมื่อกำหนดเฟรม เราสามารถเก็บสิ่งนี้ไว้ในตัวแปรคงที่ได้เนื่องจากโดยปกติแล้วจะมีเพียง tabView เดียวในโครงการ (หากของคุณมีมากกว่าหนึ่งรายการคุณจะต้องปรับเปลี่ยน)
extension UITabBar {

    static var size: CGSize = .zero

    open override var frame: CGRect {
        get {
            super.frame
        } set {
            UITabBar.size = newValue.size
            super.frame = newValue
        }
    }
}
  1. คุณจะต้องเปลี่ยนเขาonReceiveที่ด้านล่างของKeyboardHostมุมมองเพื่อพิจารณาความสูงของแถบแท็บ:
.onReceive(showPublisher.merge(with: hidePublisher)) { (height) in
            self.keyboardHeight = max(height - UITabBar.size.height, 0)
        }
  1. เท่านี้เอง! ง่ายสุด ๆ 🎉.

1

ฉันใช้วิธีการที่แตกต่างกันโดยสิ้นเชิงโดยการขยายUIHostingControllerและปรับ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()
    }
}

แล้วเปลี่ยนSceneDelegateมาใช้MyHostingControllerแทนUIHostingController .

เมื่อเสร็จแล้วฉันไม่ต้องกังวลกับแป้นพิมพ์ภายในรหัส SwiftUI ของฉัน

(หมายเหตุ: ฉันยังใช้สิ่งนี้ไม่เพียงพอเพื่อให้เข้าใจถึงผลกระทบใด ๆ ของการทำสิ่งนี้!)


1

นี่คือวิธีที่ฉันจัดการกับคีย์บอร์ดใน SwiftUI สิ่งที่ต้องจำไว้ก็คือการคำนวณบน VStack ที่แนบมา

คุณใช้บน View เป็น Modifier ทางนี้:

struct LogInView: View {

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

ดังนั้นในการมาถึงตัวปรับแต่งนี้ก่อนอื่นให้สร้างส่วนขยายของ UIResponder เพื่อรับตำแหน่ง TextField ที่เลือกใน VStack:

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

ตอนนี้คุณสามารถสร้าง KeyboardModifier โดยใช้ Combine เพื่อหลีกเลี่ยงแป้นพิมพ์ที่ซ่อน TextField:

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

สำหรับ iOS 14 (เบต้า 4) นั้นทำงานได้ค่อนข้างง่าย:

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

และขนาดของมุมมองจะปรับไปที่ด้านบนของแป้นพิมพ์ มีการปรับแต่งเพิ่มเติมอย่างแน่นอนด้วยเฟรม (.maxHeight: ... ) ฯลฯ คุณจะคิดออก

น่าเสียดายที่แป้นพิมพ์ลอยบน iPad ยังคงทำให้เกิดปัญหาเมื่อย้าย แต่วิธีแก้ปัญหาดังกล่าวข้างต้นก็เช่นกันและยังคงเป็นเบต้าฉันหวังว่าพวกเขาจะเข้าใจ

ขอบคุณ Apple ในที่สุด!


สิ่งนี้ใช้ไม่ได้เลย (14.1) มีความคิดอย่างไร?
Burgler-dev

0

มุมมองของฉัน:

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

VM ของฉัน:

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

โดยทั่วไปการจัดการช่องว่างด้านล่างตามความสูงของแป้นพิมพ์


-3

คำตอบที่สวยหรูที่สุดที่ฉันจัดการได้คล้ายกับวิธีแก้ปัญหาของ rraphael สร้างชั้นเรียนเพื่อฟังเหตุการณ์แป้นพิมพ์ แทนที่จะใช้ขนาดแป้นพิมพ์เพื่อแก้ไขช่องว่างภายในให้ส่งคืนค่าลบของขนาดแป้นพิมพ์และใช้ตัวปรับแต่ง. offset (y :) เพื่อปรับค่าออฟเซ็ตของคอนเทนเนอร์มุมมองด้านนอกสุด มันเคลื่อนไหวได้ดีพอและใช้ได้กับทุกมุมมอง


คุณทำให้สิ่งนี้เป็นภาพเคลื่อนไหวได้อย่างไร ฉันมี.offset(y: withAnimation { -keyboard.currentHeight })แต่เนื้อหากระโดดแทนที่จะเป็นภาพเคลื่อนไหว
jjatie

ไม่กี่ครั้งก่อนหน้านี้ที่ฉันใช้รหัสนี้ แต่ในช่วงเวลาของความคิดเห็นก่อนหน้านี้การแก้ไขค่าชดเชยของ vstack ระหว่างรันไทม์เป็นสิ่งที่จำเป็นทั้งหมด SwiftUI จะทำให้การเปลี่ยนแปลงของคุณเคลื่อนไหว
pcallycat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.