Skip to content

Commit

Permalink
fix: 🐛 [IOSSDKBUG-482]iPad popover FilterFeedback list jumping (#934)
Browse files Browse the repository at this point in the history
  • Loading branch information
restaurantt authored Dec 11, 2024
1 parent 7410056 commit 3149a5b
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 0 deletions.
117 changes: 117 additions & 0 deletions Sources/FioriSwiftUICore/Utils/FioriPopoverSize.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import SwiftUI

struct PopoverSizeModifier<PopoverContent: View>: ViewModifier {
@Binding var isPresented: Bool
var arrowEdge: Edge = .top
var popoverSize: CGSize? = nil
let popoverContent: () -> PopoverContent

func body(content: Content) -> some View {
content
.background(
Wrapper(isPresented: self.$isPresented, popoverSize: self.popoverSize, popoverContent: self.popoverContent, arrowEdge: self.arrowEdge)
.frame(maxWidth: .infinity, maxHeight: .infinity)
)
}

struct Wrapper<PopoverView: View>: UIViewControllerRepresentable {
@Binding var isPresented: Bool
let popoverSize: CGSize?
var popoverContent: () -> PopoverView
var arrowEdge: Edge = .top

func makeUIViewController(context: UIViewControllerRepresentableContext<Wrapper<PopoverView>>) -> WrapperViewController<PopoverView> {
WrapperViewController(
popoverSize: self.popoverSize,
arrowEdge: self.arrowEdge,
popoverContent: self.popoverContent
) {
self.isPresented = false
}
}

func updateUIViewController(_ uiViewController: WrapperViewController<PopoverView>, context: UIViewControllerRepresentableContext<Wrapper<PopoverView>>) {
uiViewController.updateSize(self.popoverSize)

if self.isPresented {
uiViewController.arrowEdge = self.arrowEdge
uiViewController.showPopover()
} else {
uiViewController.hidePopover()
}
if let hostingController = uiViewController.popoverVC as? UIHostingController<PopoverView> {
hostingController.rootView = self.popoverContent()
}
}
}

class WrapperViewController<PopoverView: View>: UIViewController, UIPopoverPresentationControllerDelegate {
var popoverSize: CGSize?
var popoverContent: () -> PopoverView
let onDismiss: () -> Void
var arrowEdge: Edge = .top

var popoverVC: UIViewController?

@available(*, unavailable)
required init?(coder: NSCoder) { fatalError("") }

init(popoverSize: CGSize?, arrowEdge: Edge = .top, popoverContent: @escaping () -> PopoverView, onDismiss: @escaping () -> Void) {
self.popoverSize = popoverSize
self.popoverContent = popoverContent
self.onDismiss = onDismiss
self.arrowEdge = arrowEdge
super.init(nibName: nil, bundle: nil)
}

func showPopover() {
guard self.popoverVC == nil else { return }
let vc = UIHostingController(rootView: popoverContent())
if let size = popoverSize { vc.preferredContentSize = size }
vc.modalPresentationStyle = .popover

if let popover = vc.popoverPresentationController {
popover.sourceView = view
switch self.arrowEdge {
case .top:
popover.permittedArrowDirections = .up
case .bottom:
popover.permittedArrowDirections = .down
case .leading:
popover.permittedArrowDirections = .left
case .trailing:
popover.permittedArrowDirections = .right
}
popover.delegate = self
}

self.popoverVC = vc
self.present(vc, animated: true, completion: nil)
}

func hidePopover() {
guard let vc = popoverVC, !vc.isBeingDismissed else { return }
vc.dismiss(animated: true, completion: nil)
self.popoverVC = nil
}

func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
self.popoverVC = nil
self.onDismiss()
}

func updateSize(_ size: CGSize?) {
self.popoverSize = size
if let vc = popoverVC, let size {
vc.preferredContentSize = size
}
}

func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
if UIDevice.current.userInterfaceIdiom == .phone {
return .automatic
}
return .none
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,14 @@ struct PickerMenuItem: View {

@ViewBuilder
var list: some View {
if UIDevice.current.userInterfaceIdiom == .phone {
self.phoneView()
} else {
self.padView()
}
}

private func phoneView() -> some View {
FilterFeedbackBarItem(leftIcon: icon(name: self.item.icon, isVisible: true), title: self.item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: self.item.isChecked)
.onTapGesture {
self.isSheetVisible.toggle()
Expand Down Expand Up @@ -375,6 +383,7 @@ struct PickerMenuItem: View {
} updateSearchListPickerHeight: { height in
self.detentHeight = max(height, 88)
}
.animation(.easeInOut)
.frame(maxHeight: UIDevice.current.userInterfaceIdiom != .phone ? self.detentHeight : nil)
.padding(0)
.onReceive(NotificationCenter.default.publisher(for: UIApplication.keyboardDidShowNotification)) { notif in
Expand Down Expand Up @@ -404,6 +413,75 @@ struct PickerMenuItem: View {
})
}

private func padView() -> some View {
FilterFeedbackBarItem(leftIcon: icon(name: self.item.icon, isVisible: true), title: self.item.label, rightIcon: Image(systemName: "chevron.down"), isSelected: self.item.isChecked)
.contentShape(Rectangle())
.onTapGesture {
self.isSheetVisible.toggle()
}
.modifier(PopoverSizeModifier(isPresented: self.$isSheetVisible, arrowEdge: self.barItemFrame.arrowDirection(), popoverSize: CGSize(width: self.popoverWidth, height: self.calculateDetentHeight()), popoverContent: {
CancellableResettableDialogNavigationForm {
SortFilterItemTitle(title: self.item.name)
} cancelAction: {
_Action(actionText: NSLocalizedString("Cancel", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: {
self.item.cancel()
self.isSheetVisible.toggle()
})
.buttonStyle(CancelButtonStyle())
} resetAction: {
if self.item.resetButtonConfiguration.isHidden {
EmptyView()
} else {
_Action(actionText: self.item.resetButtonConfiguration.title, didSelectAction: {
if self.item.resetButtonConfiguration.type == .reset {
self.item.reset()
} else {
self.item.clearAll()
}
})
.buttonStyle(ResetButtonStyle())
.disabled(self.resetButtonDisable())
}
} applyAction: {
_Action(actionText: NSLocalizedString("Apply", tableName: "FioriSwiftUICore", bundle: Bundle.accessor, comment: ""), didSelectAction: {
self.item.apply()
self.onUpdate()
self.isSheetVisible.toggle()
})
.buttonStyle(ApplyButtonStyle())
} components: {
SearchListPickerItem(value: self.$item.workingValue, valueOptions: self.item.valueOptions, hint: nil, allowsMultipleSelection: self.item.allowsMultipleSelection, allowsEmptySelection: self.item.allowsEmptySelection, isSearchBarHidden: self.item.isSearchBarHidden, disableListEntriesSection: self.item.disableListEntriesSection, allowsDisplaySelectionCount: self.item.allowsDisplaySelectionCount, barItemFrame: self.barItemFrame) { index in
self.item.onTap(option: self.item.valueOptions[index])
} selectAll: { isAll in
self.item.selectAll(isAll)
} updateSearchListPickerHeight: { height in
self.detentHeight = max(height, 88)
}
.padding(0)
.onReceive(NotificationCenter.default.publisher(for: UIApplication.keyboardDidShowNotification)) { notif in
let rect = (notif.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect) ?? .zero
self._keyboardHeight = rect.height
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.keyboardDidHideNotification)) { _ in
self._keyboardHeight = 0
}
}
}))
.ifApply(UIDevice.current.userInterfaceIdiom != .phone, content: { v in
v.background(GeometryReader { geometry in
Color.clear
.onAppear {
self.barItemFrame = geometry.frame(in: .global)
}
.setOnChange(of: geometry.frame(in: .global), action1: { newValue in
self.barItemFrame = newValue
}) { _, newValue in
self.barItemFrame = newValue
}
})
})
}

private func calculateDetentHeight() -> CGFloat {
let isNotIphone = UIDevice.current.userInterfaceIdiom != .phone
var height = self.detentHeight
Expand Down

0 comments on commit 3149a5b

Please sign in to comment.