I'm using tutorial from here https://www.hackingwithswift.com/quick-start/swiftui/how-to-convert-a-swiftui-view-to-an-image
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
And I want to apply this code to
Text("Hello world!")
.swipeActions(edge: .leading) {
Button {
total += 1
} label: {
Label("Add", systemImage: "plus.circle")
}
.tint(.indigo)
}
.swipeActions(edge: .trailing) {
Button {
total -= 1
} label: {
Label {
Text("Hello")
} icon: {
Image(uiImage:
Text("World")
.foregroundColor(.red)
.snapshot() // Here is usage
)
}
}
}
But in result I get in this particular case empty gray button (and in my real code runtime error about changing in time of redrawing)
What's wrong?
Related
My goal is to convert SwiftUI view to image. I am currently building it for iOS 15
here is the code that converts the view to image
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
Expected result: https://share.cleanshot.com/5Pvzb7
Actual result: https://share.cleanshot.com/O4GKUF
Below is the body of the view that has a button and image view
var body: some View {
VStack {
imageView
Spacer(minLength: 200)
Button {
UIImageWriteToSavedPhotosAlbum(imageView.snapshot(), nil, nil, nil)
} label: {
Label("Save to Photos", systemImage: "photo")
}
}
}
Imageview is hardcoded with data and image is loaded from the internet
var imageView: some View {
VStack {
ZStack(alignment: .top) {
Color.red
VStack {
VStack {
HStack(alignment: .top) {
Rectangle.init().frame(width: 56, height: 56).cornerRadius(28)
VStack {
HStack {
Text.init("Yura Filin").font(.system(size: 16))
Spacer()
}.padding(.bottom, 1)
HStack {
Text.init("qweqwewqeqqweqeasd asd asd aosidhsa doaid adoaid adiad hiu uh i hiu ih uhuih iuhiu ih asdi ").lineLimit(90).multilineTextAlignment(.leading).font(.system(size: 16))
Spacer()
}.padding(.bottom, 2)
HStack {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: (286 - 64 - 12))
.onReceive(imageLoader.didChange) { data in
self.image = UIImage(data: data) ?? UIImage() }.cornerRadius(14)
Spacer()
}
Spacer()
}.padding(.leading, 12).readSize { size in
print(size)
}
}
}.padding(16).background(.green).cornerRadius(12)
}.padding(64)
}
}.ignoresSafeArea()
}
Any help is appreciated. Thanks!
Unlike when a usual view is rendered and the sizing of that view is constrained by the device, that doesn't happen here. So you need to apply a frame with defined width (and possibly height) to the view you're capturing.
When I needed to do this, I created another view that isn't rendered to the user and used it solely for rendering the image. That way, any sizing you have to define in the frame doesn't mess up what the user sees.
It might take you a while to get it right, but try adding a frame modifier with desired width to the view you're rendering. 😊
Here is the code as shown below which solves your problem and in order to call UIImageWriteToSavedPhotosAlbum() you must add the NSPhotoLibraryAddUsageDescription key to your Info.plist
import SwiftUI
struct ContentView: View {
var imageView: some View {
VStack {
ZStack(alignment: .top) {
Color.red
VStack {
VStack {
HStack(alignment: .top) {
Rectangle.init().frame(width: 56, height: 56).cornerRadius(28)
VStack {
HStack {
Text.init("Yura Filin").font(.system(size: 16))
Spacer()
}.padding(.bottom, 1)
HStack {
Text.init("qweqwewqeqqweqeasd asd asd aosidhsa doaid adoaid adiad hiu uh i hiu ih uhuih iuhiu ih asdi ").lineLimit(90).multilineTextAlignment(.leading).font(.system(size: 16))
Spacer()
}.padding(.bottom, 2)
HStack {
Image("image")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: (286 - 64 - 12))
.cornerRadius(14)
Spacer()
}
Spacer()
}.padding(.leading, 12)
}
}.padding(16).background(.green).cornerRadius(12)
}.padding(64)
}
}.ignoresSafeArea()
}
var body: some View {
VStack {
Button("Save to Photos") {
let image = imageView.snapshot()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
imageView
Spacer(minLength: 200)
Button("Save to Photos") {
let image = imageView.snapshot()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
}
}
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Output :
I have the following code I am testing out for the sake of learning about MatchedGeometry and more.
struct ContentView: View {
#State var match = false
#Namespace var namespace
#State var snapshot:UIImage?
var body: some View {
ZStack {
if !match {
AView { img in
snapshot = img
withAnimation {
match.toggle()
}
}
.frame(width: 300, height: 300)
.matchedGeometryEffect(id: "1", in: namespace, properties: .frame)
}
if match && snapshot != nil {
Color.clear.ignoresSafeArea().overlay(
BView(image: snapshot!)
.matchedGeometryEffect(id: "1", in: namespace, properties: .frame)
.frame(width: 600, height: 600)
.transition(.scale)
.onTapGesture {
withAnimation {
match.toggle()
}
}
)
}
}
// .statusBar(hidden: true) // <-- THIS REMOVES THE RED GAP IN BView, but I want to keep the status bar. I want BView to work regadless.
}
}
struct AView: View {
var didTap:(_ img:UIImage)->()
var body: some View {
GeometryReader { geo in
ZStack(alignment: .center) {
Image("island")
.resizable()
.scaledToFill()
}
// .frame(width: geo.size.width, height: geo.size.height)
.border(.purple)
.onTapGesture {
didTap(snapshot(size: geo.size))
}
}
.border(.yellow)
}
}
struct BView: View {
#State var image:UIImage
var body: some View {
ZStack {
Color.red.ignoresSafeArea()
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
}
}
}
extension View {
func snapshot(origin: CGPoint = .zero, size: CGSize = .zero) -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = size == .zero ? controller.view.intrinsicContentSize : size
view?.backgroundColor = .clear
view?.bounds = CGRect(origin: origin, size: targetSize)
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
It shows this:
There are two things I am not sure why they work the way they do:
Why am I getting a gap in the BView (shown in red)? I was expecting the image to fill BView and not have that top gap. I tried ignoring save area etc... but no luck.
Why is the image in the AView not centered horizontally?
How can I fix both, meaning, I want the image to be centered and fill BView (aspect fill), and I want the image in AView to be horizontally centered.
Image comes from assets, file I found at:
https://cruisewhitsundays.imgix.net/2019/05/DayDream_Dec2018_03_0395.jpg
I think you're over-using ZStack in your AView and BView ...
Take a look at this, and see if you get the desired results:
struct ContentView: View {
#State var match = false
#Namespace var namespace
#State var snapshot:UIImage?
var body: some View {
ZStack {
if !match {
AView { img in
snapshot = img
withAnimation {
match.toggle()
}
}
.frame(width: 300, height: 300)
.clipped()
.matchedGeometryEffect(id: "1", in: namespace, properties: .frame)
}
if match && snapshot != nil {
Color.clear.ignoresSafeArea().overlay(
BView(image: snapshot!)
.matchedGeometryEffect(id: "1", in: namespace, properties: .frame)
.frame(width: 600, height: 600)
.clipped()
.transition(.scale)
.onTapGesture {
withAnimation {
match.toggle()
}
}
)
}
}
}
}
struct AView: View {
var didTap:(_ img:UIImage)->()
var body: some View {
GeometryReader { geo in
Image("island")
.resizable()
.scaledToFill()
.border(.purple)
.onTapGesture {
didTap(snapshot(size: geo.size))
}
}
.border(.yellow)
}
}
struct BView: View {
#State var image:UIImage
var body: some View {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
}
}
extension View {
func snapshot(origin: CGPoint = .zero, size: CGSize = .zero) -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = size == .zero ? controller.view.intrinsicContentSize : size
view?.backgroundColor = .clear
view?.bounds = CGRect(origin: origin, size: targetSize)
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
I found this code for taking a snapshot of a view in SwiftUI, and also found this gist for how to bring up UIActivityController in SwiftUI. It works ok but the biggest issue I am having is when you tap share the UIActivityController is blank, if you tap share again it will work as expected but I can't figure out why it doesn't work the first time? If I change to a static image or text to share it works as expected? Any thoughts?
import SwiftUI
//construct enum to decide which sheet to present:
enum ActiveSheet: String, Identifiable { // <--- note that it's now Identifiable
case photoLibrary, shareSheet
var id: String {
return self.rawValue
}
}
struct ShareHomeView: View {
#State private var shareCardAsImage: UIImage? = nil
#State var activeSheet: ActiveSheet? = nil // <--- now an optional property
var shareCard: some View {
ZStack {
VStack {
Spacer()
LinearGradient(
gradient: Gradient(colors: [.black, .red]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.cornerRadius(10.0)
.padding(.horizontal)
Spacer()
}
SubView()
.padding(.horizontal)
VStack {
HStack {
HStack(alignment: .center) {
Image(systemName: "gamecontroller")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: 40)
.padding(.leading)
VStack(alignment: .leading, spacing: 3) {
Text("My App")
.foregroundColor(.white)
.font(.headline)
.fontWeight(.bold)
Text("Wed 30 Mar 22")
.foregroundColor(.white)
.font(.headline)
// .fontWeight(.bold)
}
}
Spacer()
}
.padding([.leading, .top])
Spacer()
}
} //End of ZStack
.frame(height: 350)
}
var body: some View {
NavigationView {
VStack {
HStack {
Spacer()
Button {
self.activeSheet = .photoLibrary
} label: {
Image(systemName: "photo")
.resizable()
.scaledToFit()
.frame(height: 40)
}
.padding(.trailing)
}
//GeometryReader { geometry in
shareCard
// } //End of GeometryReader
Button(action: {
shareCardAsImage = shareCard.asImage()
self.activeSheet = .shareSheet
}) {
HStack {
Image(systemName: "square.and.arrow.up")
.font(.system(size: 20))
Text("Share")
.font(.headline)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 50, maxHeight: 50)
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(20)
}
.padding(.horizontal)
} //End of Master VStack
//sheet choosing view to display based on selected enum value:
.sheet(item: $activeSheet) { sheet in // <--- sheet is of type ActiveSheet and lets you present the appropriate sheet based on which is active
switch sheet {
case .photoLibrary:
Text("TODO")
case .shareSheet:
if let unwrappedImage = shareCardAsImage {
ShareSheet(photo: unwrappedImage)
}
}
}
//Needed to Wrap in a Navigation View and hide title so that dark mode would work, otherwise this sheet was always in the iPhone's light or dark mode
.navigationBarHidden(true)
.navigationTitle("")
}
}
}
struct RecoveryShareHomeView_Previews: PreviewProvider {
static var previews: some View {
ShareHomeView().preferredColorScheme(.dark)
ShareHomeView().preferredColorScheme(.light)
}
}
extension View {
func asImage() -> UIImage {
let controller = UIHostingController(rootView: self)
// locate far out of screen
controller.view.frame = CGRect(x: 0, y: CGFloat(Int.max), width: 1, height: 1)
UIApplication.shared.windows.first!.rootViewController?.view.addSubview(controller.view)
let size = controller.sizeThatFits(in: UIScreen.main.bounds.size)
controller.view.bounds = CGRect(origin: .zero, size: size)
controller.view.sizeToFit()
let image = controller.view.asImage()
controller.view.removeFromSuperview()
return image
}
}
extension UIView {
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
// [!!] Uncomment to clip resulting image
// rendererContext.cgContext.addPath(
// UIBezierPath(roundedRect: bounds, cornerRadius: 20).cgPath)
// rendererContext.cgContext.clip()
// As commented by #MaxIsom below in some cases might be needed
// to make this asynchronously, so uncomment below DispatchQueue
// if you'd same met crash
// DispatchQueue.main.async {
layer.render(in: rendererContext.cgContext)
// }
}
}
}
import LinkPresentation
//This code is from https://gist.github.com/tsuzukihashi/d08fce005a8d892741f4cf965533bd56
struct ShareSheet: UIViewControllerRepresentable {
let photo: UIImage
func makeUIViewController(context: Context) -> UIActivityViewController {
//let text = ""
//let itemSource = ShareActivityItemSource(shareText: text, shareImage: photo)
let activityItems: [Any] = [photo]
let controller = UIActivityViewController(
activityItems: activityItems,
applicationActivities: nil)
return controller
}
func updateUIViewController(_ vc: UIActivityViewController, context: Context) {
}
}
struct SubView: View {
var body: some View {
HStack {
Image(systemName: "star")
Text("Test View")
Image(systemName: "star")
}
}
}
Add [shareCardAsImage] so that the current value is captured inside sheet:
.sheet(item: $activeSheet) { [shareCardAsImage] sheet in
This is necessary because your item doesn't capture it explicitly, which is generally how item is used. You could also solve it by adding an associated value on your ActiveSheet that stores the image in item.
Hi I have code for writing to the camera in Swift and SwiftUI. I'm making use of the following resource for the camera and integrated with my SwiftUI view. https://betterprogramming.pub/effortless-swiftui-camera-d7a74abde37e
For some reason, when I go to a new view after I capture a photo, and then I go back to the camera with present.wrappedValue.dismiss(), this following piece of code gets triggered from CameraService.swift:
let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice)
if session.canAddInput(videoDeviceInput) {
session.addInput(videoDeviceInput)
self.videoDeviceInput = videoDeviceInput
} else {
print("Couldn't add video device input to the session.")
setupResult = .configurationFailed
session.commitConfiguration()
return
}
and it outputs that Couldn't add video device input to the session.
Please let me know what could be this issue because it seems to be that Navigation Link is the problem as I can go around from another view to the camera view and it works fine. However, if I dismiss from my navigated view using navigation link, the code for the camera breaks.
Minimal reproducible example by modifying CameraView.swift code from the above link, as well as the GitHub repo to the link:
https://github.com/rorodriguez116/SwiftCamera
import SwiftUI
import Combine
import AVFoundation
final class CameraModel: ObservableObject {
private let service = CameraService()
#Published var photo: Photo!
#Published var showAlertError = false
#Published var isFlashOn = false
#Published var willCapturePhoto = false
var alertError: AlertError!
var session: AVCaptureSession
private var subscriptions = Set<AnyCancellable>()
init() {
self.session = service.session
service.$photo.sink { [weak self] (photo) in
guard let pic = photo else { return }
self?.photo = pic
}
.store(in: &self.subscriptions)
service.$shouldShowAlertView.sink { [weak self] (val) in
self?.alertError = self?.service.alertError
self?.showAlertError = val
}
.store(in: &self.subscriptions)
service.$flashMode.sink { [weak self] (mode) in
self?.isFlashOn = mode == .on
}
.store(in: &self.subscriptions)
service.$willCapturePhoto.sink { [weak self] (val) in
self?.willCapturePhoto = val
}
.store(in: &self.subscriptions)
}
func configure() {
service.checkForPermissions()
service.configure()
}
func capturePhoto() {
service.capturePhoto()
}
func flipCamera() {
service.changeCamera()
}
func zoom(with factor: CGFloat) {
service.set(zoom: factor)
}
func switchFlash() {
service.flashMode = service.flashMode == .on ? .off : .on
}
}
struct CameraView: View {
#StateObject var model = CameraModel()
#State var currentZoomFactor: CGFloat = 1.0
#State var showNewPost: Bool = false
var captureButton: some View {
Button(action: {
model.capturePhoto()
}, label: {
Circle()
.foregroundColor(.white)
.frame(width: 80, height: 80, alignment: .center)
.overlay(
Circle()
.stroke(Color.black.opacity(0.8), lineWidth: 2)
.frame(width: 65, height: 65, alignment: .center)
)
})
}
var capturedPhotoThumbnail: some View {
Group {
if model.photo != nil {
Image(uiImage: model.photo.image!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 60, height: 60)
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
.animation(.spring())
.onAppear{
self.showNewPost.toggle()
print("show new post is \(self.showNewPost)")
}
} else {
RoundedRectangle(cornerRadius: 10)
.frame(width: 60, height: 60, alignment: .center)
.foregroundColor(.black)
}
}
}
var flipCameraButton: some View {
Button(action: {
model.flipCamera()
}, label: {
Circle()
.foregroundColor(Color.gray.opacity(0.2))
.frame(width: 45, height: 45, alignment: .center)
.overlay(
Image(systemName: "camera.rotate.fill")
.foregroundColor(.white))
})
}
var body: some View {
GeometryReader { reader in
NavigationView {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
VStack {
Button(action: {
model.switchFlash()
}, label: {
Image(systemName: model.isFlashOn ? "bolt.fill" : "bolt.slash.fill")
.font(.system(size: 20, weight: .medium, design: .default))
})
.accentColor(model.isFlashOn ? .yellow : .white)
CameraPreview(session: model.session)
.gesture(
DragGesture().onChanged({ (val) in
// Only accept vertical drag
if abs(val.translation.height) > abs(val.translation.width) {
// Get the percentage of vertical screen space covered by drag
let percentage: CGFloat = -(val.translation.height / reader.size.height)
// Calculate new zoom factor
let calc = currentZoomFactor + percentage
// Limit zoom factor to a maximum of 5x and a minimum of 1x
let zoomFactor: CGFloat = min(max(calc, 1), 5)
// Store the newly calculated zoom factor
currentZoomFactor = zoomFactor
// Sets the zoom factor to the capture device session
model.zoom(with: zoomFactor)
}
})
)
.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
}
}
)
.animation(.easeInOut)
HStack {
capturedPhotoThumbnail
Spacer()
captureButton
Spacer()
flipCameraButton
}
.padding(.horizontal, 20)
}
NavigationLink(destination: NewPost(), isActive: $showNewPost) {
}
}
}
}
}
}
struct NewPost: View {
#Environment(\.presentationMode) var present
var body: some View {
Button(action: {
print("tapped button")
present.wrappedValue.dismiss() //triggers the configuration failed
}, label: {
Text("new post").foregroundColor(Color.black)
})
}
}
Hi I wanted to know if its possible to disable the drag down gesture to dismiss the full screen cover in swift / swiftui.
Here is my code:
import SwiftUI
import Combine
import AVFoundation
import CropViewController
final class CameraModel: ObservableObject {
private let service = CameraService()
#Published var photo: Photo!
#Published var showAlertError = false
#Published var isFlashOn = false
#Published var willCapturePhoto = false
var alertError: AlertError!
var session: AVCaptureSession
private var subscriptions = Set<AnyCancellable>()
init() {
self.session = service.session
service.$photo.sink { [weak self] (photo) in
guard let pic = photo else { return }
self?.photo = pic
}
.store(in: &self.subscriptions)
service.$shouldShowAlertView.sink { [weak self] (val) in
self?.alertError = self?.service.alertError
self?.showAlertError = val
}
.store(in: &self.subscriptions)
service.$flashMode.sink { [weak self] (mode) in
self?.isFlashOn = mode == .on
}
.store(in: &self.subscriptions)
service.$willCapturePhoto.sink { [weak self] (val) in
self?.willCapturePhoto = val
}
.store(in: &self.subscriptions)
}
func configure() {
service.checkForPermissions()
service.configure()
//service.changeCamera()
}
func capturePhoto() {
if photo != nil {
self.photo = nil
}
service.capturePhoto()
}
func flipCamera() {
service.changeCamera()
}
func zoom(with factor: CGFloat) {
service.set(zoom: factor)
}
func switchFlash() {
service.flashMode = service.flashMode == .on ? .off : .on
}
func resetPhoto(){
if photo != nil {
self.photo = nil
}
}
}
struct CameraView: View {
#StateObject var model = CameraModel()
#State var currentZoomFactor: CGFloat = 1.0
#StateObject var registerData = RegisterViewModel()
#StateObject var newPostData = NewPostModel()
enum SheetType {
case imagePick
case imageCrop
case share
}
#State private var currentSheet: SheetType = .imagePick
#State private var actionSheetIsPresented = false
#State private var sheetIsPresented = false
#State private var originalImage: UIImage?
#State private var image: UIImage?
#State private var croppingStyle = CropViewCroppingStyle.default
#State private var croppedRect = CGRect.zero
#State private var croppedAngle = 0
var captureButton: some View {
Button(action: {
let impactMed = UIImpactFeedbackGenerator(style: .light)
impactMed.impactOccurred()
model.capturePhoto()
}, label: {
Circle()
.foregroundColor(.white)
.frame(width: 80, height: 80, alignment: .center)
.overlay(
Circle()
.stroke(Color.black.opacity(0.8), lineWidth: 2)
.frame(width: 65, height: 65, alignment: .center)
)
})
}
var capturedPhotoThumbnail: some View {
Group {
RoundedRectangle(cornerRadius: 10)
.frame(width: 55, height: 55, alignment: .center)
.foregroundColor(Color.gray.opacity(0.2))
.onTapGesture(perform: {
// newPostData.picker.toggle()
self.croppingStyle = .default
self.currentSheet = .imagePick
self.sheetIsPresented = true
})
.overlay(
Image("gallery")
.renderingMode(.template)
.resizable()
.frame(width: 25, height: 25)
.foregroundColor(Color("white")))
.sheet(isPresented: $sheetIsPresented) {
if (self.currentSheet == .imagePick) {
ImagePickerView(croppingStyle: self.croppingStyle, sourceType: .photoLibrary, onCanceled: {
// on cancel
}) { (image) in
guard let image = image else {
return
}
self.originalImage = image
DispatchQueue.main.async {
self.currentSheet = .imageCrop
self.sheetIsPresented = true
}
}
} else if (self.currentSheet == .imageCrop) {
ZStack {
Color("imagecropcolor").edgesIgnoringSafeArea(.all)
ImageCropView(croppingStyle: self.croppingStyle, originalImage: self.originalImage!, onCanceled: {
// on cancel
}) { (image, cropRect, angle) in
// on success
self.image = image
model.resetPhoto()
newPostData.newPost.toggle()
}
}
}
}
}
}
var flipCameraButton: some View {
Button(action: {
let impactMed = UIImpactFeedbackGenerator(style: .light)
impactMed.impactOccurred()
model.flipCamera()
}, label: {
Circle()
.foregroundColor(Color.gray.opacity(0.2))
.frame(width: 45, height: 45, alignment: .center)
.overlay(
Image(systemName: "camera.rotate.fill")
.foregroundColor(.white))
})
}
var body: some View {
GeometryReader { reader in
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
VStack {
HStack{
Button(action: {
model.switchFlash()
}, label: {
Image(systemName: model.isFlashOn ? "bolt.fill" : "bolt.slash.fill")
.font(.system(size: 20, weight: .medium, design: .default))
})
.accentColor(model.isFlashOn ? .yellow : .white)
.padding(.leading, 30)
Spacer()
if model.photo != nil {
Text("taken photo").onAppear{
newPostData.newPost.toggle()
}
}
// Image(uiImage: model.photo.image!)
// .resizable()
// .aspectRatio(contentMode: .fill)
// .frame(width: 60, height: 60)
// .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
// .animation(.spring())
//
}
CameraPreview(session: model.session)
.gesture(
DragGesture().onChanged({ (val) in
// Only accept vertical drag
if abs(val.translation.height) > abs(val.translation.width) {
// Get the percentage of vertical screen space covered by drag
let percentage: CGFloat = -(val.translation.height / reader.size.height)
// Calculate new zoom factor
let calc = currentZoomFactor + percentage
// Limit zoom factor to a maximum of 5x and a minimum of 1x
let zoomFactor: CGFloat = min(max(calc, 1), 5)
// Store the newly calculated zoom factor
currentZoomFactor = zoomFactor
// Sets the zoom factor to the capture device session
model.zoom(with: zoomFactor)
}
})
)
.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
}
}
)
.animation(.easeInOut)
HStack {
capturedPhotoThumbnail
Spacer()
captureButton
.padding(.trailing, 10)
Spacer()
flipCameraButton
}
.padding(.horizontal, 20)
.padding(.bottom, 20)
}
}.fullScreenCover(isPresented: $newPostData.newPost) {
if model.photo == nil {
NewPost(imageData: (self.image?.pngData())! )
} else {
NewPost(imageData: model.photo.originalData)
}
}
}
}
}
Edit: I've added my full code showing both the.fullscreencover at the bottom of the CameraView along with the .sheet for the image picker
I don't want to dismiss NewView() by dragging down from the top. Is this possible? Thanks!