调整键盘出现/消失的视图:动画曲线问题

时间:2020-04-04 01:32:32

标签: ios swiftui

我正在尝试创建一个SwiftUI视图修饰符,以响应屏幕键盘的出现或消失来添加底部填充。这是我正在使用的方法:

struct KeyboardAdjustingModifier: ViewModifier {
    @ObservedObject private var responder = KeyboardResponder()

    func body(content: Content) -> some View {
        GeometryReader { proxy in
            content
                .padding(.bottom, self.bottomPadding(in: proxy))
        }
    }

    func bottomPadding(in proxy: GeometryProxy) -> CGFloat {
        if responder.endFrame == .zero {
            return 0
        } else {
            return max(0, proxy.frame(in: .global).maxY - self.responder.endFrame.minY)
        }
    }
}

fileprivate final class KeyboardResponder: ObservableObject {
    @Published var endFrame: CGRect = .zero

    init(notificationCenter: NotificationCenter = .default) {
        notificationCenter.addObserver(self,
                                       selector: #selector(keyboardWillShowOrChangeFrame(notification:)),
                                       name: UIResponder.keyboardWillShowNotification, object: nil)
        notificationCenter.addObserver(self,
                                       selector: #selector(keyboardWillShowOrChangeFrame(notification:)),
                                       name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
        notificationCenter.addObserver(self,
                                       selector: #selector(keyboardWillHide(notification:)),
                                       name: UIResponder.keyboardWillHideNotification, object: nil)
    }

    @objc private func keyboardWillShowOrChangeFrame(notification: Notification) {
        withAnimation(Animation(curve: notification.animationCurve, duration: notification.duration)) {
            endFrame = notification.endFrame
        }
    }

    @objc private func keyboardWillHide(notification: Notification) {
        withAnimation(Animation(curve: notification.animationCurve, duration: notification.duration)) {
            endFrame = .zero
        }
    }

    struct Info {
        let curve: UIView.AnimationCurve
        let duration: TimeInterval
        let endFrame: CGRect
    }
}

fileprivate extension Animation {
    init(curve: UIView.AnimationCurve, duration: TimeInterval) {
        switch curve {
        case .easeInOut:
            self = .easeInOut(duration: duration)
        case .easeIn:
            self = .easeIn(duration: duration)
        case .easeOut:
            self = .easeOut(duration: duration)
        case .linear:
            self = .linear(duration: duration)
        @unknown default:
            self = .easeInOut(duration: duration)
        }
    }
}

fileprivate extension Notification {
    var animationCurve: UIView.AnimationCurve {
        guard let rawValue = (userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int) else {
            return UIView.AnimationCurve.linear
        }

        return UIView.AnimationCurve(rawValue: rawValue)!
    }

    var duration: TimeInterval {
        userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0
    }

    var endFrame: CGRect {
        userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect ?? .zero
    }
}

我遇到的问题是,看来SwiftUI动画曲线与键盘的动画曲线完全不同:

Animation curve mismatch

有人知道像这样可以将SwiftUI动画与UIKit同步吗?我如何将动画曲线信息从通知的userInfo映射到SwiftUI动画曲线,这很可能会出错。还是我应该考虑采用完全不同的方法解决这个问题?

我尝试的另一件事是使用UIHostingController来直接在UIKit中处理动画。它可以与键盘正确同步,但是如果SwiftUI也同时在对动画进行动画处理,则在动画块中调用layoutIfNeeded时会崩溃。

1 个答案:

答案 0 :(得分:0)

这是我正在使用的,它几乎可以正常工作:

演示

demo

代码

public extension View {
    func adaptToKeyboard() -> some View {
        modifier(AdaptToSoftwareKeyboard())
    }
}

public struct AdaptToSoftwareKeyboard: ViewModifier {
    @State var currentHeight: CGFloat = 0

    public func body(content: Content) -> some View {
        content
            .padding(.bottom, currentHeight)
            .edgesIgnoringSafeArea(.bottom)
            .animation(.spring())
            .onAppear(perform: subscribeToKeyboardEvents)
    }

    private func subscribeToKeyboardEvents() {
        NotificationCenter.Publisher(
            center: NotificationCenter.default,
            name: UIResponder.keyboardWillShowNotification
        )
            .compactMap { $0.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect }
            .map { $0.height }
            .subscribe(Subscribers.Assign(object: self, keyPath: \.currentHeight))

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

用法

someView()
    .adaptToKeyboard()

在此处通过演示进行测试:https://github.com/youjinp/SwiftUIKit