Ika
Ika

Reputation: 1773

How to make the bottom button follow the keyboard display in SwiftUI

With the help of the following, I was able to follow the button on the keyboard display. However, animation cannot be applied well.

How to show complete List when keyboard is showing up in SwiftUI

import SwiftUI
import Combine
import UIKit

class KeyboardResponder: ObservableObject {
    let willset = PassthroughSubject<CGFloat, Never>()
    private var _center: NotificationCenter
    @Published var currentHeight: CGFloat = 0
    var keyboardDuration: TimeInterval = 0

    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) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
            currentHeight = keyboardSize.height

            guard let duration:TimeInterval = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return }
            keyboardDuration = duration
        }
    }

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

struct Content: View {
    @ObservedObject var keyboard = KeyboardResponder()

    var body: some View {
        VStack {
            Text("text")

            Spacer()

            NavigationLink(destination: SubContentView()) {
                Text("Done")
            }
        }
        .padding(.bottom, keyboard.currentHeight)
        animation(Animation.easeInOut(duration: keyboard.keyboardDuration))
    }
}

enter image description here

Upvotes: 3

Views: 4090

Answers (1)

kontiki
kontiki

Reputation: 40489

Your main problem, is that you are using an implicit animation. Not only it may be animating things you may not want to animate, but also, you should never apply .animation() on containers. Of the few warnings in SwiftUI's documentation, this is one of them:

Use this modifier on leaf views rather than container views. The animation applies to all child views within this view; calling animation(_:) on a container view can lead to unbounded scope.

Source: https://developer.apple.com/documentation/swiftui/view/3278508-animation

The modified code removes the implicit .animation() call and replaces it with two implicit withAnimation closures.

I also replaced keyboardFrameEndUserInfoKey with keyboardFrameEndUserInfoKey, second calls were giving useless geometry.

class KeyboardResponder: ObservableObject {
    let willset = PassthroughSubject<CGFloat, Never>()
    private var _center: NotificationCenter
    @Published var currentHeight: CGFloat = 0
    var keyboardDuration: TimeInterval = 0

    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) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {

            guard let duration:TimeInterval = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return }
            keyboardDuration = duration

            withAnimation(.easeInOut(duration: duration)) {
                self.currentHeight = keyboardSize.height
            }

        }
    }

    @objc func keyBoardWillHide(notification: Notification) {
        guard let duration:TimeInterval = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return }

        withAnimation(.easeInOut(duration: duration)) {
            currentHeight = 0
        }
    }
}


struct ContentView: View {
    @ObservedObject var keyboard = KeyboardResponder()

    var body: some View {

        return VStack {
            Text("text \(keyboard.currentHeight)")

            TextField("Enter text", text: .constant(""))
            Spacer()

            NavigationLink(destination: Text("SubContentView()")) {
                Text("Done")
            }
        }
        .padding(.bottom, keyboard.currentHeight)
//        .animation(Animation.easeInOut(duration: keyboard.keyboardDuration))
    }
}

Upvotes: 6

Related Questions