UIViewRepresentable view disappears when contextMenu is activated - swiftui

I'm trying use context menu with UIViewRepresentable.
When context menu is activated, UIViewRepresentable disappears.
Here is the code:
UIViewRepresentable view:
struct TestView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 150, height: 150))
view.backgroundColor = UIColor.red
return view
}
func updateUIView(_ uiView: UIViewType, context: Context) {
//
}
}
ContentView:
struct ContentView: View {
var body: some View {
TestView()
.frame(width: 200, height: 200)
.contextMenu {
Text("Context Menu")
}
}
}
How to make UIViewRepresentable not disappear?

I ran into this exact issue recently with my UIViewRepresentable view that returns a custom UIImageView. The fix was adding a clipped() modifier to the view with the context menu. Not sure if this applies to your case though as mine involved an image.
This was tested on iPhone 12 Pro running iOS 15.1.

I ran into this same issue and found that the clipped() modifier works fine on iOS 15.0 and later. But it doesn't fix the problem on iOS 14.
As I was experimenting with different approaches I discovered that the compositingGroup() fixes the problem on iOS 14.0 and later including iOS 15.
struct ContentView: View {
var body: some View {
TestView()
.frame(width: 200, height: 200)
.compositingGroup()
.contextMenu {
Text("Context Menu")
}
}
}
UIViewRepresentable rendered correctly within a contextMenu

Related

How to make a custom UIView Appear/Dissapear in SwiftUI

I have a CameraView in my app that I'd like to bring up whenever a button is to be presssed. It's a custom view that looks like this
// The CameraView
struct Camera: View {
#StateObject var model = CameraViewModel()
#State var currentZoomFactor: CGFloat = 1.0
#Binding var showCameraView: Bool
// MARK: [main body starts here]
var body: some View {
GeometryReader { reader in
ZStack {
// This black background lies behind everything.
Color.black.edgesIgnoringSafeArea(.all)
CameraViewfinder(session: model.session)
.onAppear {
model.configure()
}
.alert(isPresented: $model.showAlertError, content: {
Alert(title: Text(model.alertError.title), message: Text(model.alertError.message), dismissButton: .default(Text(model.alertError.primaryButtonTitle), action: {
model.alertError.primaryAction?()
}))
})
.scaledToFill()
.ignoresSafeArea()
.frame(width: reader.size.width,height: reader.size.height )
// Buttons and controls on top of the CameraViewfinder
VStack {
HStack {
Button {
//
} label: {
Image(systemName: "xmark")
.resizable()
.frame(width: 20, height: 20)
.tint(.white)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
Spacer()
flashButton
}
HStack {
capturedPhotoThumbnail
Spacer()
captureButton
Spacer()
flipCameraButton
}
.padding([.horizontal, .bottom], 20)
.frame(maxHeight: .infinity, alignment: .bottom)
}
} // [ZStack Ends Here]
} // [Geometry Reader Ends here]
} // [Main Body Ends here]
// More view component code goes here but I've excluded it all for brevity (they don't add anything substantial to the question being asked.
} // [End of CameraView]
It contains a CameraViewfinder View which conforms to the UIViewRepresentable Protocol:
struct CameraViewfinder: UIViewRepresentable {
class VideoPreviewView: UIView {
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
}
let session: AVCaptureSession
func makeUIView(context: Context) -> VideoPreviewView {
let view = VideoPreviewView()
view.backgroundColor = .black
view.videoPreviewLayer.cornerRadius = 0
view.videoPreviewLayer.session = session
view.videoPreviewLayer.connection?.videoOrientation = .portrait
return view
}
func updateUIView(_ uiView: VideoPreviewView, context: Context) {
}
}
I wish to add a binding property to this camera view that allows me to toggle this view in and out of my screen like any other social media app would allow. Here's an example
#State var showCamera: Bool = false
var body: some View {
mainTabView
.overlay {
CameraView(showCamera: $showCamera)
}
}
I understand that the code to achieve this must be written inside the updateUIView() method. Now, although I'm quite familiar with SwiftUI, I'm relatively inexperienced with UIKit, so any help on this and any helpful resources that could help me better code situations similar to this would be greatly appreciated.
Thank you.
EDIT: Made it clear that the first block of code is my CameraView.
EDIT2: Added Example of how I'd like to use the CameraView in my App.
Judging by the way you would like to use it in the app, the issue seems to not be with the CameraViewFinder but rather with the way in which you want to present it.
A proper SwiftUI way to achieve this would be to use a sheet like this:
#State var showCamera: Bool = false
var body: some View {
mainTabView
.sheet(isPresented: $showCamera) {
CameraView()
.interactiveDismissDisabled() // Disables swipe to dismiss
}
}
If you don't want to use the sheet presentation and would like to cover the whole screen instead, then you should use the .fullScreenCover() modifier like this.
#State var showCamera: Bool = false
var body: some View {
mainTabView
.overlay {
CameraView()
.fullScreenCover(isPresented: $showCamera)
}
}
Either way you would need to somehow pass the state to your CameraView to allow the presented screen to set the state to false and therefore dismiss itself, e.g. with a button press.

Navigation bug when dismissing view while focussing on empty .searchable() modifier

When trying to navigate back from a view using the environment Dismiss value while also focussing on an empty searchable modifier the view you navigated back to becomes unresponsive. This is due to an empty UIView blocking any interaction with the view as seen in this screenshot:
Empty UIView blocking view after navigating back
This only occurs when the searchbar is focussed and empty when trying to navigate back. When there's a value in the searchbar everything works:
GIF of the bug
Am I doing something wrong here?
Tested on Xcode 14.2 iPhone 14 Pro (iOS 16.0) simulator.
import SwiftUI
struct MainPage: View {
var body: some View {
if #available(iOS 16.0, *) {
NavigationStack {
Text("Main view")
NavigationLink(destination: DetailView()) {
Text("Click me")
}
}
}
}
}
struct DetailView: View {
#Environment(\.dismiss) private var dismiss
#State private var searchText = ""
var body: some View {
VStack {
Text("Detail view")
Button("Go back") {
dismiss()
}
}
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
}
}
This bug only seems to happen when using NavigationStack or NavigationView with a .navigationViewStyle(.stack). When using NavigationView without a navigationViewStyle it seems to work fine. Currently I can work around this using the latter but I would prefer to use NavigationStack as NavigationView has become deprecated since iOS 16.0.
Any help is appreciated.

Center MPVolumeView vertically in SwiftUI view

I want to place an MPVolumeView in my SwiftUI view but it doesn't behave like a normal SwiftUI view. I want the volume slider to be centered vertically between the two dividers. If you replaced VolumeSlider with Text it would be centered. How can I make the VolumeSlider behave in the same way?
// Must be run on real device, not simulator
import SwiftUI
import MediaPlayer
struct ContentView: View {
var body: some View {
VStack {
Divider()
VolumeSlider()
.frame(height: 128)
Divider()
}
}
}
struct VolumeSlider: UIViewRepresentable {
func makeUIView(context: Context) -> MPVolumeView {
MPVolumeView(frame: .zero)
}
func updateUIView(_ view: MPVolumeView, context: Context) {}
}
Firstly this behavior is in fact normal for a SwiftUI view. You are specifying a point height for the VolumeSlider. If you decrease the 128 it will not have the space between slider and bottom divider.
struct ContentView: View {
var body: some View {
VStack {
Divider()
VolumeSlider()
.frame(height: 50)
Divider()
}
}
}
I'll update this answer to try and make it not take up the whole space when .frame is removed.

SwiftUI and tvOS: adjustsImageWhenAncestorFocused

How do you get the same focus effect in SwiftUI for images as you can with UIKit? I see you can use the card button style and it does provide motion effects but not the parallax that adjustsImageWhenAncestorFocused provides.
struct ContentView: View {
var body: some View {
Button {
print("tapped")
} label: {
AsyncImage(url: URL(string: "Image-URL"))
.frame(width: 300, height: 300)
}
.buttonStyle(.card)
}
}
I know this question is pretty old, but I ran into the same problem and found that wrapping your UIImageView in a UIViewRepresentable works just fine.

Unexpected visual behavior of SwiftUI Context Menu with UIViewRepresentable

UIViewRepresentable:
import SwiftUI
import MapKit
import CoreData
struct CustomView: UIViewRepresentable {
var coordinate: CLLocationCoordinate2D
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ uiView: MKMapView, context: Context) {
let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
let region = MKCoordinateRegion(center: coordinate, span: span)
uiView.setRegion(region, animated: true)
}
}
ContentView:
import SwiftUI
import CoreLocation
struct ContentView: View {
var body: some View {
GeometryReader { geo in
CustomView(coordinate: CLLocationCoordinate2D(latitude: 37.33182, longitude: -122.03118))
.frame(width: geo.size.width, height: 300)
.cornerRadius(25)
.contextMenu(/*#START_MENU_TOKEN#*/ContextMenu(menuItems: {
Text("Menu Item 1")
Text("Menu Item 2")
Text("Menu Item 3")
})/*#END_MENU_TOKEN#*/)
}
.padding(.horizontal)
.frame(height: 300)
}
}
The above code works fine in the SwiftUI Canvas and the Simulator, however on my physical testing device (an iPhone 7 - iOS 14 Beta 5), when I long press the CustomView, it becomes black. The app also sometimes crashes with the following error which may be related:
CGImageCreate: invalid image alphaInfo: kCGImageAlphaNone. It should be kCGImageAlphaNoneSkipLast
If I replace the CustomView with an Image like below, everything works as expected:
import SwiftUI
struct ContentView: View {
var body: some View {
Image("imageName")
.resizable()
.frame(width: 350, height: 300)
.cornerRadius(25)
.contextMenu(/*#START_MENU_TOKEN#*/ContextMenu(menuItems: {
Text("Menu Item 1")
Text("Menu Item 2")
Text("Menu Item 3")
})/*#END_MENU_TOKEN#*/)
}
}
How can I fix it? Thanks!