I used whole code from this site to create custom SwiftUI popover.
I wanted to test it, so I created custom model:
struct Model {
var someInit = 0
}
And then created ContentView:
struct ContentView: View {
@State private var model: Model = .init()
@State private var bool: Bool = false
var body: some View {
Button("Press me") {
bool.toggle()
}
.alwaysPopover(isPresented: $bool) {
Picker("", selection: $model.someInit){
ForEach(1...30, id: \.self){
Text("\($0)")
.tag($0)
}
}
.pickerStyle(.wheel)
}
}
}
And code for custom popover is
private extension UIResponder {
func closestVC() -> UIViewController? {
var responder: UIResponder? = self
while responder != nil {
if let controller = responder as? UIViewController {
return controller
}
responder = responder?.next
}
return nil
}
}
private struct InternalAnchorView: UIViewRepresentable {
typealias UIViewType = UIView
let uiView: UIView
func makeUIView(context: Self.Context) -> Self.UIViewType {
uiView
}
func updateUIView(_ uiView: Self.UIViewType, context: Self.Context) { }
}
extension View {
public func alwaysPopover<Content>(isPresented: Binding<Bool>, @ViewBuilder content: @escaping () -> Content) -> some View where Content : View {
self.modifier(AlwaysPopoverModifier(isPresented: isPresented, contentBlock: content))
}
}
struct AlwaysPopoverModifier<PopoverContent>: ViewModifier where PopoverContent: View {
// Workaround for the missing `@StateObject` in iOS 13.
private struct Store {
var anchorView = UIView()
}
@State private var store = Store()
func body(content: Content) -> some View {
if isPresented.wrappedValue {
presentPopover()
}
return content
.background(InternalAnchorView(uiView: store.anchorView))
}
let isPresented: Binding<Bool>
let contentBlock: () -> PopoverContent
private func presentPopover() {
let contentController = ContentViewController(rootView: contentBlock(), isPresented: isPresented)
contentController.modalPresentationStyle = .popover
let view = store.anchorView
guard let popover = contentController.popoverPresentationController else { return }
popover.sourceView = view
popover.sourceRect = view.bounds
popover.delegate = contentController
guard let sourceVC = view.closestViewControllerResponder() else { return }
if let presentedVC = sourceVC.presentedViewController {
presentedVC.dismiss(animated: true) {
sourceVC.present(contentController, animated: true)
}
} else {
sourceVC.present(contentController, animated: true)
}
}
}
class ContentViewController<V>: UIHostingController<V>, UIPopoverPresentationControllerDelegate where V:View {
var isPresented: Binding<Bool>
init(rootView: V, isPresented: Binding<Bool>) {
self.isPresented = isPresented
super.init(rootView: rootView)
}
@MainActor required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
self.isPresented.wrappedValue = false
}
override func viewDidLoad() {
super.viewDidLoad()
let size = sizeThatFits(in: UIView.layoutFittingExpandedSize)
preferredContentSize = size
}
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .none
}
}
But I noticed that, before dismissing popover, values from ForEach are reseted to initial value before dismissing popover.
I tried to use .onChange and track value changing and using .id, but unsuccessfully.




