I'm the fullScreenCover to show a modal in my SwiftUI project.
However, I need to show the modal with a transparent and blurred background.
But I can't seem to be able to achieve this at all.
This is my code:
.fullScreenCover(isPresented: $isPresented) {
VStack(spacing: 20) {
Spacer()
.frame(maxWidth: .infinity, minHeight: 100)
.background(Color.black)
.opacity(0.3)
//Text("modal")
}
.background(SecondView()) // << helper !!
}
And I have this on the same View:
struct BackgroundBlurView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let view = UIVisualEffectView(effect: UIBlurEffect(style: .light))
DispatchQueue.main.async {
view.superview?.superview?.backgroundColor = .clear
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
The above code will open the fullscreen modal but its not transparent or blurred at all.
This is what I get:
is there something else I need to do?
Reference to where I got the above code from:
SwiftUI: Translucent background for fullScreenCover
I removed some extraneous stuff and made sure that there was content in the background that you could see peeking through.
Without the alpha that is suggested in a different answer, the effect is very subtle, but there.
struct ContentView : View {
#State var isPresented = false
var body: some View {
VStack {
Text("Some text")
Spacer()
Text("More")
Spacer()
Button(action: {
isPresented.toggle()
}) {
Text("Show fullscreenview")
}
.fullScreenCover(isPresented: $isPresented) {
Text("modal")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(BackgroundBlurView())
}
Spacer()
Text("More text")
}
}
}
struct BackgroundBlurView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let view = UIVisualEffectView(effect: UIBlurEffect(style: .light))
DispatchQueue.main.async {
view.superview?.superview?.backgroundColor = .clear
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
Note the extremely subtle blue you can see from the Button shining through. More obvious in dark mode.
The effect may get more dramatic as you add more color and content to the background view. Adding alpha certainly makes things more obvious, though.
#Asperi answer from here is totally correct. Just need to add alpha to blur view.
struct BackgroundBlurView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let view = UIVisualEffectView(effect: UIBlurEffect(style: .light))
view.alpha = 0.5 //< --- here
DispatchQueue.main.async {
view.superview?.superview?.backgroundColor = .clear
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
Another approach is to use ViewControllerHolder reference link
struct ViewControllerHolder {
weak var value: UIViewController?
}
struct ViewControllerKey: EnvironmentKey {
static var defaultValue: ViewControllerHolder {
return ViewControllerHolder(value: UIApplication.shared.windows.first?.rootViewController)
}
}
extension EnvironmentValues {
var viewController: UIViewController? {
get { return self[ViewControllerKey.self].value }
set { self[ViewControllerKey.self].value = newValue }
}
}
extension UIViewController {
func present<Content: View>(backgroundColor: UIColor = UIColor.clear, #ViewBuilder builder: () -> Content) {
let toPresent = UIHostingController(rootView: AnyView(EmptyView()))
toPresent.view.backgroundColor = backgroundColor
toPresent.modalPresentationStyle = .overCurrentContext
toPresent.modalTransitionStyle = .coverVertical
toPresent.rootView = AnyView(
builder()
.environment(\.viewController, toPresent)
)
//Add blur efect
let blurEfect = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
blurEfect.frame = UIScreen.main.bounds
blurEfect.alpha = 0.5
toPresent.view.insertSubview(blurEfect, at: 0)
self.present(toPresent, animated: true, completion: nil)
}
}
struct ContentViewNeo: View {
#Environment(\.viewController) private var viewControllerHolder: UIViewController?
var body: some View {
VStack {
Text("Background screen")
Button("Open") {
viewControllerHolder?.present(builder: {
SheetView()
})
}
}
}
}
struct SheetView: View {
var body: some View {
Text("Sheet view")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
Related
I am attempting to wrap the GADBannerView with a UIViewControllerRepresentable so that I can use it in my SwiftUI views but I encountered a situation where the background layer of the viewController covers the main list view. This resulted in a situation where the list is not scrollable.
I would like the bannerView to be anchored to the bottom of the view, but just above the tabView.
Code:
struct LaunchView: View {
var body: some View {
TabView {
HomeView()
.tabItem {
Label {
Text("Home")
} icon: {
Image(systemName: "gear")
}
}
}
}
}
struct HomeView: View {
var body: some View {
NavigationView {
ZStack {
List {
ForEach(0..<20, id: \.self) { _ in
Text("Hello, World!")
}
}
VStack {
Spacer()
JRBannerView()
}
}
.navigationTitle(Text("Home"))
}
}
}
class JRBannerViewController: UIViewController {
lazy var bannerView: GADBannerView = {
let v = GADBannerView()
v.translatesAutoresizingMaskIntoConstraints = false
v.adUnitID = TEST_AD_ID
v.rootViewController = self
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
loadBannerAd()
}
func setupViews() {
view.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 0.5) //<== obscures the main list view behind
view.addSubview(bannerView)
bannerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
bannerView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}
func loadBannerAd() {
let viewWidth = view.frame.inset(by: view.safeAreaInsets).size.width
bannerView.adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(viewWidth)
bannerView.load(GADRequest())
}
}
struct JRBannerView: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> JRBannerViewController {
return JRBannerViewController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
Results:
How do I compact the background view of the viewController to the same height of the bannerView so that the list is scrollable?
I have a custom camera view which uses UIKit to capture pictures and store it in a CameraViewModel in my SwiftUI project. The CameraPreview is what acts as the view Finder for my camera view and uses AVFoundation:
struct CameraPreview: 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) {
}
}
and the I use this in my CameraView.swift body as such
#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)
CameraPreview(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?()
}))
})
.overlay(
Group {
if model.willCapturePhoto {
Color.black
}
}
)
.scaledToFill()
.ignoresSafeArea()
.frame(width: reader.size.width,height: reader.size.height )
// .animation(.easeInOut)
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]
I wish to open the camera View when someone presses a button somewhere on my app, like so
#State var showCamera: Bool = false
var body: some View {
mainTabView
.overlay {
CameraView(showCamera: $showCamera)
}
}
But when I do this in my app, no matter where I put the camera overlay, it stays open and the close button does nothing to close the camera view either. I'm pretty sure this is my fundamental lack of how views are constructed in UIKit and how the UIViewRepresentable works but I'd like some help regardless on how I'd achieve the desired effect. Also, any resources to understand how this works would also be greatly appreciated. Thank you.
I have new SwiftUI .keyboard toolbar added. And it works great with Swiftui TextFields. But I consider if it is possible and how can it be done to use this toolbar also with UITextFields wrapped in UIViewRepresentable. I don’t know if I am doing something wrong or this isn’t supported.
I had the same problem and couldn't find an answer, so I tried to recreate this keyboard toolbar in SwiftUI. Here is the code:
struct SomeView: View {
#State var text = ""
#State var focusedUITextField = false
var body: some View {
NavigationStack {
ZStack {
VStack {
Button("Remove UITextField focus") {
focusedUITextField = false
}
TextField("SwiftUI", text: $text)
CustomTextField(hint: "UIKit", text: $text, focused: $focusedUITextField)
.fixedSize(horizontal: false, vertical: true)
}
.padding(.horizontal)
if focusedUITextField {
VStack(spacing: 0) {
Spacer()
Divider()
keyboardToolbarContent
.frame(maxWidth: .infinity, maxHeight: 44)
.background(Color(UIColor.secondarySystemBackground))
}
}
}
/*
.toolbar {
ToolbarItem(placement: .keyboard) {
keyboardToolbarContent
}
}
*/
}
}
var keyboardToolbarContent: some View {
HStack {
Rectangle()
.foregroundColor(.red)
.frame(width: 50, height: 40)
Text("SwiftUI stuff")
}
}
}
And for the custom UITextField:
struct CustomTextField: UIViewRepresentable {
let hint: String
#Binding var text: String
#Binding var focused: Bool
func makeUIView(context: Context) -> UITextField {
let uiTextField = UITextField()
uiTextField.delegate = context.coordinator
uiTextField.placeholder = hint
return uiTextField
}
func updateUIView(_ uiTextField: UITextField, context: Context) {
uiTextField.text = text
uiTextField.placeholder = hint
if focused {
uiTextField.becomeFirstResponder()
} else {
uiTextField.resignFirstResponder()
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, UITextFieldDelegate {
let parent: CustomTextField
init(parent: CustomTextField) {
self.parent = parent
}
func textFieldDidBeginEditing(_ textField: UITextField) {
parent.focused = true
}
func textFieldDidEndEditing(_ textField: UITextField) {
parent.focused = false
}
}
}
Unfortunately the animation of the custom toolbar and the keyboard don't match perfectly. I would challenge the guy in the comments from this post How to add a keyboard toolbar in SwiftUI that remains even when keyboard not visible but sadly I can't comment.
Also the background color doesn't match the native toolbar exactly, I don't know which color is used there.
Using Swift 2.0 I am hoping to find a way to capture the resized image after the user has selected how they want to see it in the frame from the scroll view (ZoomScrollView).
I know there are complex examples out there from Swift but was hoping to find a simpler way to capture this in Swift 2.0. In all my searching I've heard references to using ZStack and some masks or overlays but can't find a simple good example.
I am hoping someone can update my example with the ZStack, masks, etc and how to extract the image for saving or provide a better example.
import SwiftUI
struct ContentView: View {
#Environment(\.presentationMode) var presentationMode
#State var isAccepted: Bool = false
#State var isShowingImagePicker = false
#State var isShowingActionPicker = false
#State var sourceType:UIImagePickerController.SourceType = .camera
#State var image:UIImage?
var body: some View {
HStack {
Color(UIColor.systemYellow).frame(width: 8)
VStack(alignment: .leading) {
HStack {
Spacer()
VStack {
if image != nil {
ZoomScrollView {
Image(uiImage: image!)
.resizable()
.scaledToFit()
}
.frame(width: 300, height: 300, alignment: .center)
.clipShape(Circle())
} else {
Image(systemName: "person.crop.circle")
.resizable()
.font(.system(size: 32, weight: .light))
.frame(width: 300, height: 300, alignment: .center)
.cornerRadius(180)
.foregroundColor(Color(.systemGray))
.clipShape(Circle())
}
}
Spacer()
}
Spacer()
HStack {
Button(action: {
self.isShowingActionPicker = true
}, label: {
Text("Select Image")
.foregroundColor(.blue)
})
.frame(width: 130)
.actionSheet(isPresented: $isShowingActionPicker, content: {
ActionSheet(title: Text("Select a profile avatar picture"), message: nil, buttons: [
.default(Text("Camera"), action: {
self.isShowingImagePicker = true
self.sourceType = .camera
}),
.default(Text("Photo Library"), action: {
self.isShowingImagePicker = true
self.sourceType = .photoLibrary
}),
.cancel()
])
})
.sheet(isPresented: $isShowingImagePicker) {
imagePicker(image: $image, isShowingImagePicker: $isShowingImagePicker ,sourceType: self.sourceType)
}
Spacer()
// Save button
Button(action: {
// Save Image here... print for now just see if file dimensions are the right size
print("saved: ", image!)
self.presentationMode.wrappedValue.dismiss()
}
) {
HStack {
Text("Save").foregroundColor(isAccepted ? .gray : .blue)
}
}
.frame(width: 102)
.padding(.top)
.padding(.bottom)
//.buttonStyle(RoundedCorners())
.disabled(isAccepted) // Disable if if already isAccepted is true
}
}
Spacer()
Color(UIColor.systemYellow).frame(width: 8)
}
.padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top)
.background(Color(UIColor.systemYellow))
}
}
struct ZoomScrollView<Content: View>: UIViewRepresentable {
private var content: Content
init(#ViewBuilder content: () -> Content) {
self.content = content()
}
func makeUIView(context: Context) -> UIScrollView {
// set up the UIScrollView
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator // for viewForZooming(in:)
scrollView.maximumZoomScale = 20
scrollView.minimumZoomScale = 1
scrollView.bouncesZoom = true
// create a UIHostingController to hold our SwiftUI content
let hostedView = context.coordinator.hostingController.view!
hostedView.translatesAutoresizingMaskIntoConstraints = true
hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostedView.frame = scrollView.bounds
scrollView.addSubview(hostedView)
return scrollView
}
func makeCoordinator() -> Coordinator {
return Coordinator(hostingController: UIHostingController(rootView: self.content))
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
// update the hosting controller's SwiftUI content
context.coordinator.hostingController.rootView = self.content
assert(context.coordinator.hostingController.view.superview == uiView)
}
// MARK: - Coordinator
class Coordinator: NSObject, UIScrollViewDelegate {
var hostingController: UIHostingController<Content>
init(hostingController: UIHostingController<Content>) {
self.hostingController = hostingController
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return hostingController.view
}
}
}
struct imagePicker:UIViewControllerRepresentable {
#Binding var image: UIImage?
#Binding var isShowingImagePicker: Bool
typealias UIViewControllerType = UIImagePickerController
typealias Coordinator = imagePickerCoordinator
var sourceType:UIImagePickerController.SourceType = .camera
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = sourceType
picker.delegate = context.coordinator
return picker
}
func makeCoordinator() -> imagePickerCoordinator {
return imagePickerCoordinator(image: $image, isShowingImagePicker: $isShowingImagePicker)
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
}
class imagePickerCoordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#Binding var image: UIImage?
#Binding var isShowingImagePicker: Bool
init(image:Binding<UIImage?>, isShowingImagePicker: Binding<Bool>) {
_image = image
_isShowingImagePicker = isShowingImagePicker
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let uiimage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
image = uiimage
isShowingImagePicker = false
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
isShowingImagePicker = false
}
}
Just want to return the image that's zoomed in the circle. The image can be square (re: the 300x300 frame), that's fine just need the zoomed image not whole screen or the original image.
the following changes were successful based the comments:
Add the following State variables:
#State private var rect: CGRect = .zero
#State private var uiimage: UIImage? = nil // resized image
Added "RectGetter" to the picked image frame after image selected selected
if image != nil {
ZoomScrollView {
Image(uiImage: image!)
.resizable()
.scaledToFit()
}
.frame(width: 300, height: 300, alignment: .center)
.clipShape(Circle())
.background(RectGetter(rect: $rect))
Here is the struct and extension I added
extension UIView {
func asImage(rect: CGRect) -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: rect)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
struct RectGetter: View {
#Binding var rect: CGRect
var body: some View {
GeometryReader { proxy in
self.createView(proxy: proxy)
}
}
func createView(proxy: GeometryProxy) -> some View {
DispatchQueue.main.async {
self.rect = proxy.frame(in: .global)
}
return Rectangle().fill(Color.clear)
}
}
Last I set the image to save
self.uiimage = UIApplication.shared.windows[0].rootViewController?.view.asImage(rect: self.rect)
This assumes the root controller. However, in my production app I had to point to self
self.uiimage = UIApplication.shared.windows[0].self.asImage(rect: self.rect)
Then I was able to save that image.
A couple of notes. The image returned is the rectangle which is fine. However due to the way the image is captured the rest of the rectangle outside the cropShape of a circle has the background color. In this case yellow at the for corners outside the circle. There is probably a way to have some sort of ZOrder mask that overlays the image for display when you are resizing the image but then this accesses the right layer and saves the full rectangle picture. If anyone wants to suggest further that would be a cleaner solution but this works assuming you will always display the picture in the same crop shape it was saved in.
I'm trying to use Material components MDCCard in SwiftUI and trying to make its size exactly as its subviews but I end up with stretched size to fill any available space in its parent.
How could I make its size wraps its content.
Here is my MaterialCard
struct MaterialCard<Content: View>: UIViewRepresentable {
let content: () -> Content
func makeUIView(context: Context) -> MDCCard {
let card = MDCCard()
let view = UIHostingController(rootView: content()).view!
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
card.addSubview(view)
return card
}
func updateUIView(_ card: MDCCard, context: Context) {
card.setContentHuggingPriority(.defaultHigh, for: .vertical)
card.setContentHuggingPriority(.defaultHigh, for: .horizontal)
}
}
And here is the preview used
struct CCard_Previews: PreviewProvider {
static var previews: some View {
VStack {
MaterialCard(rippled: true) {
Text("HEllllO")
.frame(width: 200, height: 200)
.background(Color.red.opacity(0.5))
}
.background(Color.gray)
.environment(\.colorScheme, .light)
MaterialCard(rippled: true) {
Text("HEllllO")
.frame(width: 200, height: 200)
.background(Color.red.opacity(0.5))
}
.background(Color.black)
.environment(\.colorScheme, .dark)
}
}
}
And the result is
Result