Skip to content

Commit

Permalink
[PM-11712] Fix keyboard scroll on cipher view so it doesn't overlap c…
Browse files Browse the repository at this point in the history
…ursor (#1292)
  • Loading branch information
fedemkr authored Jan 30, 2025
1 parent c746146 commit 20b76b8
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import SwiftUI

// MARK: - View

/// Extension of `View` to have the `backport` object availalbe to ease
/// with available APIs.
///
/// Adapted from https://davedelong.com/blog/2021/10/09/simplifying-backwards-compatibility-in-swift/
///
extension View {
/// Helper to apply backport operations for available APIs.
var backport: Backport<Self> { Backport(self) }
}

// MARK: - Backport<View>

/// Backport for `View` content.
///
/// Adapted from https://davedelong.com/blog/2021/10/09/simplifying-backwards-compatibility-in-swift/
///
extension Backport where Content: View {
/// On iOS 16+, configures the scroll view to dismiss the keyboard immediately.
///
func dismissKeyboardImmediately() -> some View {
if #available(iOS 16, *) {
return content.scrollDismissesKeyboard(.immediately)
} else {
return content
}
}

/// On iOS 16+, configures the scroll view to dismiss the keyboard interactively.
///
func dismissKeyboardInteractively() -> some View {
if #available(iOS 16, *) {
return content.scrollDismissesKeyboard(.interactively)
} else {
return content
}
}

//// Configures the content margin for scroll content of a specific view.
///
/// Use this modifier to customize the content margins of different
/// kinds of views. For example, you can use this modifier to customize
/// the scroll content margins of scrollable views like ``ScrollView``. In the
/// following example, the scroll view will automatically inset
/// its content by the safe area plus an additional 20 points
/// on the leading and trailing edge.
///
/// ScrollView(.horizontal) {
/// // ...
/// }
/// .contentMargins(.horizontal, 20.0)
///
/// - Parameters:
/// - edges: The edges to add the margins to.
/// - length: The amount of margins to add.
@ViewBuilder
func scrollContentMargins(_ edges: Edge.Set = .all, _ length: CGFloat?) -> some View {
if #available(iOS 17.0, *) {
content.contentMargins(edges, length, for: .scrollContent)
} else {
content
}
}
}

// MARK: - Backport<Content>

/// Helper to deal with available APIs and provide backport operations
///
/// Adapted from https://davedelong.com/blog/2021/10/09/simplifying-backwards-compatibility-in-swift/
///
public struct Backport<Content> {
/// The content to apply backport operations.
public let content: Content

/// Initializes a backport with some content to apply backrpot operations.
/// - Parameter content: The content to apply backport operations.
public init(_ content: Content) {
self.content = content
}
}
20 changes: 0 additions & 20 deletions BitwardenShared/UI/Platform/Application/Extensions/View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,6 @@ extension View {
block(self)
}

/// On iOS 16+, configures the scroll view to dismiss the keyboard immediately.
///
func dismissKeyboardImmediately() -> some View {
if #available(iOSApplicationExtension 16, *) {
return self.scrollDismissesKeyboard(.immediately)
} else {
return self
}
}

/// On iOS 16+, configures the scroll view to dismiss the keyboard interactively.
///
func dismissKeyboardInteractively() -> some View {
if #available(iOSApplicationExtension 16, *) {
return self.scrollDismissesKeyboard(.interactively)
} else {
return self
}
}

/// Focuses next field in sequence, from the given `FocusState`.
/// Requires a currently active focus state and a next field available in the sequence.
/// (https://stackoverflow.com/a/71531523)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Combine
import Foundation
import UIKit

/// An observable responder to handle keyboard show/hide notifications.
final class KeyboardResponder: ObservableObject {
// MARK: Properties

/// Whether the keyboard is shown.
@Published var isShown: Bool = false

/// A publisher when the keyboard will hide.
var keyboardWillHideNotification = NotificationCenter.default.publisher(
for: UIResponder.keyboardWillHideNotification
)

/// A publisher when the keyboard will show.
var keyboardWillShowNotification = NotificationCenter.default.publisher(
for: UIResponder.keyboardWillShowNotification
)

// MARK: Initializer

/// Initializes a `KeyboardResponder`.
init() {
keyboardWillHideNotification.map { _ in
false
}
.assign(to: &$isShown)

keyboardWillShowNotification.map { _ in
true
}
.assign(to: &$isShown)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ struct AddEditSendItemView: View { // swiftlint:disable:this type_body_length

profileSwitcher
}
.dismissKeyboardInteractively()
.backport.dismissKeyboardInteractively()
.background(Asset.Colors.backgroundPrimary.swiftUIColor.ignoresSafeArea())
.navigationBar(
title: store.state.mode.navigationTitle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,4 +441,4 @@ private extension BitwardenSdk.LoginView {
static func usernameFixture() -> BitwardenSdk.LoginView {
.fixture(username: FakeData.email1)
}
}
} // swiftlint:disable:this file_length
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import SwiftUI
struct AddEditItemView: View {
// MARK: Private Properties

/// A responder to keyboard visibility events.
@ObservedObject private var keyboard = KeyboardResponder()

/// An object used to open urls in this view.
@Environment(\.openURL) private var openURL

Expand Down Expand Up @@ -93,12 +96,13 @@ struct AddEditItemView: View {
.padding(12)
}
.animation(.default, value: store.state.collectionsForOwner)
.dismissKeyboardImmediately()
.backport.dismissKeyboardImmediately()
.background(
Asset.Colors.backgroundPrimary.swiftUIColor
.ignoresSafeArea()
)
.navigationBarTitleDisplayMode(.inline)
.backport.scrollContentMargins(Edge.Set.bottom, keyboard.isShown ? 30.0 : 0.0)
}

@ViewBuilder private var cardItems: some View {
Expand Down

0 comments on commit 20b76b8

Please sign in to comment.