I'm trying to show the iPad Popover share sheet (which uses UIPopoverPresentationController) from a SwiftUI Button. I've been able to get it to show but it only shows in the top left origin despite the sourceView and sourceRect being set differently.
Is there something else I need to set?
This is what the code currently produces,
This is the code I have so far,
struct ContentView: View {
#State
var label = "N/A"
#State
var buttonPos = CGRect(x: 0, y: 0,
width: 0, height: 0)
var body: some View {
VStack{
Spacer(minLength: 900)
GeometryReader { geometry in
Button("Share"){
print("Share button is located at \(buttonPos)")
showShareSheetAtPopover()
}.padding()
.frame(width: geometry.size.width)
.frame(height: geometry.size.height)
.background(Color.pink)
.onAppear(perform: {
buttonPos = geometry.frame(in: .global)
let localPos = geometry.frame(in: .local)
label = "Width: \(buttonPos.size) \n Global Pos: \(buttonPos.origin) \n Local Pos: \(localPos.origin)"
})
}.background(Color.orange)
Text(label)
.padding()
.background(Color.mint)
.frame(minHeight: 0, maxHeight: .infinity)
// don't truncate text
.lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
}.background(Color.yellow)
}
func showShareSheetAtPopover(){
let vc = (UIApplication.shared.windows.first?.rootViewController)!
let shareItem = ["Some Text to Share"]
let ac = UIActivityViewController(
activityItems: shareItem,
applicationActivities: nil)
let onAnIPad = (ac.popoverPresentationController != nil)
print( onAnIPad ? "On an iPad":"Not on an iPad" )
if (onAnIPad){
let popoverPos = buttonPos
let ppc = ac.popoverPresentationController!
let sourceView = UIView(frame: popoverPos)
print("Source View Frame \(sourceView.frame)")
ppc.sourceView = sourceView
ppc.sourceRect = popoverPos
//vc.show(ac, sender: {})
vc.present(ac, animated: true)
print(ppc.frameOfPresentedViewInContainerView)
}else{
vc.show(ac, sender: {})
}
}
}
I got it kind of working by using a UIViewRepresentable to wrap a UIButton in the SwiftUI view and use that as the anchor point.
Here is the updated code, it is not at all pretty or clean.
import Foundation
import SwiftUI
final class MyUIButton: NSObject, UIViewRepresentable{
#objc let action: () -> Void
#objc var button: UIButton
init(action: #escaping () -> Void){
print("Creating button")
self.action = action
self.button = UIButton()
super.init()
self.button.setTitle("My Button", for: UIControl.State.normal)
self.button.configuration = .filled()
self.button.addTarget(self,
action: #selector(self.onButtonClick),
for: UIControl.Event.touchDown)
}
func makeUIView(context: Context) -> some UIView {
print("Making button")
return button
}
#objc func onButtonClick(){
print("onClick")
action()
}
func updateUIView(_ uiView: UIViewType, context: Context) {
print("Updating")
}
}
import SwiftUI
struct ContentView: View {
#State
var label = "N/A"
#State
var buttonPos = CGRect(x: 0, y: 0,
width: 0, height: 0)
// need to set it as a class variable otherwise the
// selector does not get called
// see https://stackoverflow.com/questions/45158000/uibutton-addtarget-selector-is-not-working
var myButton = MyUIButton(action:{
print("Clicked!")
})
var body: some View {
VStack{
Spacer(minLength: 900)
self.myButton
GeometryReader { geometry in
Button("Share"){
print("Share button is located at \(buttonPos)")
showShareSheetAtPopover()
}.padding()
.frame(width: geometry.size.width)
.frame(height: 40)
.background(Color.pink)
.onAppear(perform: {
buttonPos = geometry.frame(in: .global)
let localPos = geometry.frame(in: .local)
label = "Width: \(buttonPos.size) \n Global Pos: \(buttonPos.origin) \n Local Pos: \(localPos.origin)"
})
}.background(Color.orange)
Spacer(minLength: 50)
Text(label)
.padding()
.background(Color.mint)
.frame(minHeight: 0, maxHeight: .infinity)
// don't truncate text
.lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
}.background(Color.yellow)
}
func showShareSheetAtPopover(){
let vc = (UIApplication.shared.windows.first?.rootViewController)!
let shareItem = ["Some Text to Share"]
let ac = UIActivityViewController(
activityItems: shareItem,
applicationActivities: nil)
let onAnIPad = (ac.popoverPresentationController != nil)
print( onAnIPad ? "On an iPad":"Not on an iPad" )
if (onAnIPad){
let popoverPos = buttonPos
let ppc = ac.popoverPresentationController!
let sourceView = UIView(frame: popoverPos)
print("Source View Frame \(sourceView.frame)")
ppc.sourceView = myButton.button
//ppc.sourceRect = popoverPos
//vc.show(ac, sender: {})
vc.present(ac, animated: true)
print(ppc.frameOfPresentedViewInContainerView)
}else{
vc.show(ac, sender: {})
}
}
}
Related
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 can’t figure out how to change the color of the image icon after clicking on it, for example, if the window is active, then the color of the icon is blue, if not, then it’s gray.
here is an example of what i am asking
If you know the solution please help me
Here is the full code
This code is fully working, you can check it out
import SwiftUI
struct AllLinesView: View {
#State var currentSelection: Int = 0
var body: some View {
PagerTabView(tint: .white, selection: $currentSelection) {
Image(systemName: "bolt.fill")
.pageLabel()
Image(systemName: "flame")
.pageLabel()
Image(systemName: "person.fill")
.pageLabel()
} content: {
Color.red
.pageView(ignoresSafeArea: true, edges: .bottom)
Color.green
.pageView(ignoresSafeArea: true, edges: .bottom)
Color.yellow
.pageView(ignoresSafeArea: true, edges: .bottom)
}
.ignoresSafeArea(.container, edges: .bottom)
}
}
TabView
struct PagerTabView<Content: View, Label: View>: View {
var content: Content
var label: Label
var tint: Color
#Binding var selection: Int
init(tint:Color,selection: Binding<Int>,#ViewBuilder labels: #escaping ()->Label,#ViewBuilder content: #escaping ()->Content) {
self.content = content()
self.label = labels()
self.tint = tint
self._selection = selection
}
#State var offset: CGFloat = 0
#State var maxTabs: CGFloat = 0
#State var tabOffset: CGFloat = 0
var body: some View {
VStack(alignment: .leading,spacing: 0) {
HStack(spacing: 0) {
label
}
.overlay(
HStack(spacing: 0) {
ForEach(0..<Int(maxTabs), id: \.self) { index in
Rectangle()
.fill(Color.black.opacity(0.01))
.onTapGesture {
let newOffset = CGFloat(index) * getScreenBounds().width
self.offset = newOffset
}
}
}
)
.foregroundColor(tint)
Capsule()
.fill(tint)
.frame(width: maxTabs == 0 ? 0 : (getScreenBounds().width / maxTabs), height: 2)
.padding(.top, 10)
.frame(maxWidth: .infinity, alignment: .leading)
.offset(x: tabOffset)
OffsetPageTabView(selection: $selection,offset: $offset) {
HStack(spacing: 0) {
content
}
.overlay(
GeometryReader { proxy in
Color.clear
.preference(key: TabPreferenceKey.self, value: proxy.frame(in: .global))
}
)
.onPreferenceChange(TabPreferenceKey.self) { proxy in
let minX = -proxy.minX
let maxWidth = proxy.width
let screenWidth = getScreenBounds().width
let maxTabs = (maxWidth / screenWidth).rounded()
let progress = minX / screenWidth
let tabOffset = progress * (screenWidth / maxTabs)
self.tabOffset = tabOffset
self.maxTabs = maxTabs
}
}
}
}
}
TabPreferenceKey
struct TabPreferenceKey: PreferenceKey {
static var defaultValue: CGRect = .init()
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
pageLabel - pageView
extension View {
//IMAGE
func pageLabel()->some View {
self
.frame(maxWidth: .infinity, alignment: .center)
}
//PAGE
func pageView(ignoresSafeArea: Bool = false, edges: Edge.Set = [])->some View {
self
.frame(width: getScreenBounds().width, alignment: .center)
.ignoresSafeArea(ignoresSafeArea ? .container : .init(), edges: edges)
}
func getScreenBounds()->CGRect {
return UIScreen.main.bounds
}
}
OffsetPage
struct OffsetPageTabView<Content: View>: UIViewRepresentable {
var content: Content
#Binding var offset: CGFloat
#Binding var selection: Int
func makeCoordinator() -> Coordinator {
return OffsetPageTabView.Coordinator(parent: self)
}
init(selection: Binding<Int>,offset: Binding<CGFloat>, #ViewBuilder content: #escaping ()->Content) {
self.content = content()
self._offset = offset
self._selection = selection
}
func makeUIView(context: Context) -> UIScrollView {
let scrollview = UIScrollView()
let hostview = UIHostingController(rootView: content)
hostview.view.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
hostview.view.topAnchor.constraint(equalTo: scrollview.topAnchor),
hostview.view.leadingAnchor.constraint(equalTo: scrollview.leadingAnchor),
hostview.view.trailingAnchor.constraint(equalTo: scrollview.trailingAnchor),
hostview.view.bottomAnchor.constraint(equalTo: scrollview.bottomAnchor),
hostview.view.heightAnchor.constraint(equalTo: scrollview.heightAnchor)
]
scrollview.addSubview(hostview.view)
scrollview.addConstraints(constraints)
scrollview.isPagingEnabled = true
scrollview.showsVerticalScrollIndicator = false
scrollview.showsHorizontalScrollIndicator = false
scrollview.delegate = context.coordinator
return scrollview
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
let currentOffset = uiView.contentOffset.x
if currentOffset != offset {
print("updating")
uiView.setContentOffset(CGPoint(x: offset, y: 0), animated: true)
}
}
class Coordinator: NSObject, UIScrollViewDelegate {
var parent: OffsetPageTabView
init(parent: OffsetPageTabView) {
self.parent = parent
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.x
let maxSize = scrollView.contentSize.width
let currentSelection = (offset / maxSize).rounded()
parent.selection = Int(currentSelection)
parent.offset = offset
}
}
}
This code is not built in a way that is easily changeable. The primary issue is that it uses ViewBuilders for the labels and pages, but our (not Apple's) SwiftUI code doesn't have insight into how many elements get passed into a ViewBuilder like this. So, I had to add a somewhat ugly hack of passing the number of child views by hand. I also had to add foregroundColor modifiers explicitly for each label, which is another result of shortcomings of the way the existing code works.
The original code's currentSelection logic was completely broken (ie didn't function at all), but was easily fixable once explicitly passing the number of child elements.
See updated code including inline comments of where changes were made.
struct AllLinesView: View {
#State var currentSelection: Int = 0
var body: some View {
PagerTabView(tint: .white, selection: $currentSelection, children: 3) { //<-- Here
Image(systemName: "bolt.fill")
.pageLabel()
.foregroundColor(currentSelection == 0 ? .blue : .white) //<-- Here
Image(systemName: "flame")
.pageLabel()
.foregroundColor(currentSelection == 1 ? .blue : .white) //<-- Here
Image(systemName: "person.fill")
.pageLabel()
.foregroundColor(currentSelection == 2 ? .blue : .white) //<-- Here
} content: {
Color.red
.pageView(ignoresSafeArea: true, edges: .bottom)
Color.green
.pageView(ignoresSafeArea: true, edges: .bottom)
Color.yellow
.pageView(ignoresSafeArea: true, edges: .bottom)
}
.ignoresSafeArea(.container, edges: .bottom)
.onChange(of: currentSelection) { newValue in
print(newValue)
}
}
}
struct PagerTabView<Content: View, Label: View>: View {
var content: Content
var label: Label
var tint: Color
var children: Int //<-- Here
#Binding var selection: Int
init(tint:Color,selection: Binding<Int>,children: Int, #ViewBuilder labels: #escaping ()->Label,#ViewBuilder content: #escaping ()->Content) {
self.children = children
self.content = content()
self.label = labels()
self.tint = tint
self._selection = selection
}
#State var offset: CGFloat = 0
#State var maxTabs: CGFloat = 0
#State var tabOffset: CGFloat = 0
var body: some View {
VStack(alignment: .leading,spacing: 0) {
HStack(spacing: 0) {
label
}
.overlay(
HStack(spacing: 0) {
ForEach(0..<Int(maxTabs), id: \.self) { index in
Rectangle()
.fill(Color.black.opacity(0.01))
.onTapGesture {
let newOffset = CGFloat(index) * getScreenBounds().width
self.offset = newOffset
}
}
}
)
.foregroundColor(tint)
Capsule()
.fill(tint)
.frame(width: maxTabs == 0 ? 0 : (getScreenBounds().width / maxTabs), height: 2)
.padding(.top, 10)
.frame(maxWidth: .infinity, alignment: .leading)
.offset(x: tabOffset)
OffsetPageTabView(selection: $selection,offset: $offset, children: children) { //<-- Here
HStack(spacing: 0) {
content
}
.overlay(
GeometryReader { proxy in
Color.clear
.preference(key: TabPreferenceKey.self, value: proxy.frame(in: .global))
}
)
.onPreferenceChange(TabPreferenceKey.self) { proxy in
let minX = -proxy.minX
let maxWidth = proxy.width
let screenWidth = getScreenBounds().width
let maxTabs = (maxWidth / screenWidth).rounded()
let progress = minX / screenWidth
let tabOffset = progress * (screenWidth / maxTabs)
self.tabOffset = tabOffset
self.maxTabs = maxTabs
}
}
}
}
}
struct TabPreferenceKey: PreferenceKey {
static var defaultValue: CGRect = .init()
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
extension View {
//IMAGE
func pageLabel()->some View {
self
.frame(maxWidth: .infinity, alignment: .center)
}
//PAGE
func pageView(ignoresSafeArea: Bool = false, edges: Edge.Set = [])->some View {
self
.frame(width: getScreenBounds().width, alignment: .center)
.ignoresSafeArea(ignoresSafeArea ? .container : .init(), edges: edges)
}
func getScreenBounds()->CGRect {
return UIScreen.main.bounds
}
}
struct OffsetPageTabView<Content: View>: UIViewRepresentable {
var content: Content
#Binding var offset: CGFloat
#Binding var selection: Int
var children: Int //<-- Here
func makeCoordinator() -> Coordinator {
return OffsetPageTabView.Coordinator(parent: self)
}
init(selection: Binding<Int>,offset: Binding<CGFloat>, children: Int, #ViewBuilder content: #escaping ()->Content) {
self.content = content()
self._offset = offset
self._selection = selection
self.children = children
}
func makeUIView(context: Context) -> UIScrollView {
let scrollview = UIScrollView()
let hostview = UIHostingController(rootView: content)
hostview.view.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
hostview.view.topAnchor.constraint(equalTo: scrollview.topAnchor),
hostview.view.leadingAnchor.constraint(equalTo: scrollview.leadingAnchor),
hostview.view.trailingAnchor.constraint(equalTo: scrollview.trailingAnchor),
hostview.view.bottomAnchor.constraint(equalTo: scrollview.bottomAnchor),
hostview.view.heightAnchor.constraint(equalTo: scrollview.heightAnchor)
]
scrollview.addSubview(hostview.view)
scrollview.addConstraints(constraints)
scrollview.isPagingEnabled = true
scrollview.showsVerticalScrollIndicator = false
scrollview.showsHorizontalScrollIndicator = false
scrollview.delegate = context.coordinator
return scrollview
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
let currentOffset = uiView.contentOffset.x
if currentOffset != offset {
//print("updating")
uiView.setContentOffset(CGPoint(x: offset, y: 0), animated: true)
}
}
class Coordinator: NSObject, UIScrollViewDelegate {
var parent: OffsetPageTabView
init(parent: OffsetPageTabView) {
self.parent = parent
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset.x
let maxSize = scrollView.contentSize.width
let currentSelection = (offset / maxSize) * CGFloat(parent.children) //<-- Here
print("max: ", maxSize, offset)
print("Set selection to: ", Int(currentSelection), currentSelection)
parent.selection = Int(currentSelection)
parent.offset = offset
}
}
}
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!
I have a TabView with six pages. The animation is in the last page.
When I attend to the last page the animation shows for a split second and disappear completely.
Thought it might be problem with the animation but it works elsewhere just fine.
I present this TabView using sheet.
Last page:
struct SixScreen: View{
#EnvironmentObject var session: SessionStore
#Binding var dismiss: Bool
var body: some View{
VStack(spacing: 16){
Spacer()
LottieView(name: "complete")
.frame(width: 200, height: 200, alignment: .center)
Button(action: {
dismiss.toggle()
}, label: {
Text("Start")
.frame(width: 100, height: 50, alignment: .center)
.foregroundColor(.blue)
.background(Color.white)
.cornerRadius(10)
.shadow(color: .blue, radius: 5, x: 0, y: 1)
})
.padding(.bottom, 32)
Spacer()
}
}
}
Lottie View implementation:
struct LottieView: UIViewRepresentable {
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
var name: String!
var animationView = AnimationView()
class Coordinator: NSObject {
var parent: LottieView
init(_ animationView: LottieView) {
self.parent = animationView
super.init()
}
}
func makeUIView(context: UIViewRepresentableContext<LottieView>) -> UIView {
let view = UIView()
animationView.animation = Animation.named(name)
animationView.contentMode = .scaleAspectFit
animationView.loopMode = .loop
animationView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(animationView)
NSLayoutConstraint.activate([
animationView.widthAnchor.constraint(equalTo: view.widthAnchor),
animationView.heightAnchor.constraint(equalTo: view.heightAnchor)
])
return view
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<LottieView>) {
animationView.play()
}
}
Tab View:
Group{
TabView{
FirstScreen()
SecondScreen()
ThirdScreen()
FourthScreen()
FifthScreen()
SixScreen(dismiss: $dismiss)
}
.tabViewStyle(PageTabViewStyle())
.indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
.padding(.bottom)
}
.background(gradient)
.edgesIgnoringSafeArea(.all)
.navigationBarHidden(true)
}
Try to force refresh the view using the .id() modifier
struct AnimationView: View {
#State private var lottieID = UUID()
var body: some View {
LottieView(name: "complete")
.frame(width: 200, height: 200, alignment: .center)
.id(lottieID)
.onAppear {
lottieID = UUID()
}
}
}
After a lot of research and testing, if you're looking to keep the Lottie animation live inside the SwiftUI TabView you should add this code snippet:
public func makeUIView(context: UIViewRepresentableContext<LottieView>) -> UIView {
let view = UIView(frame: .zero)
let animation = Animation.named(lottieFile)
animationView.animation = animation
animationView.animationSpeed = animationSpeed
animationView.contentMode = .scaleAspectFit
animationView.loopMode = loopMode
animationView.backgroundBehavior = .pauseAndRestore <------
My UIImagePickerController has a white Cancel button for some reason, making it very hard to see...
Here is my ImagePicker class:
struct ImagePicker: UIViewControllerRepresentable {
let key: String
#Binding var uiImage: UIImage?
#Environment(\.presentationMode) var presentationMode
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> UIImagePickerController {
let controller = UIImagePickerController()
controller.navigationBar.topItem?.rightBarButtonItem?.tintColor = UIColor.orange
controller.navigationBar.topItem?.leftBarButtonItem?.tintColor = UIColor.orange
controller.navigationBar.backItem?.leftBarButtonItem?.tintColor = UIColor.orange
controller.sourceType = .photoLibrary
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
var parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let img = info[.originalImage] as? UIImage {
parent.uiImage = img
}
parent.presentationMode.wrappedValue.dismiss()
}
}
}
struct Dialog: View {
#Binding var dialogKey: String?
#Binding var showImagePicker: Bool
#Binding var changed: Bool
#Binding var uiImage: UIImage?
#Binding var loadingKey: String?
#State var noImage = false
var body: some View {
HStack(spacing: 0) {
Button(action: { self.showImagePicker.toggle() }){
Text(noImage ? "Add" : "Replace")
.frame(width: 80)
.multilineTextAlignment(.center)
.font(.custom("Seravek-Bold", size: 15))
.foregroundColor(Color.white)
.padding(.vertical, 15)
.background(
Rectangle()
.fill(Color("Meeq"))
)
.cornerRadius(radius: 5, corners: [.topLeft, .bottomLeft])
.shadow(color: Color.black.opacity(0.5), radius: 1, x: 0, y: 1)
}
Button(action: { self.deleteImage() }){
Text("Delete")
.frame(width: 80)
.multilineTextAlignment(.center)
.font(.custom("Seravek-Bold", size: 15))
.padding(.vertical, 15)
.foregroundColor(Color.white)
.background(
Rectangle()
.fill(Color("Exit"))
)
.shadow(color: Color.black.opacity(0.5), radius: 1, x: 0, y: 1)
.opacity(noImage ? 0.3 : 1.0)
}
.disabled(noImage)
.onAppear {
// Check if there's no image for the selected circle
if let dialogKey = dialogKey {
let key = UserDefaults.standard.string(forKey: dialogKey)
if key == nil { noImage = true }
print("Delete onAppear: \(key)")
}
}
Button(action: { self.dialogKey = nil }){
Text("Cancel")
.frame(width: 80)
.multilineTextAlignment(.center)
.font(.custom("Seravek-Bold", size: 15))
.foregroundColor(Color.white)
.padding(.vertical, 15)
.background(
Rectangle()
.fill(Color.gray)
)
.cornerRadius(radius: 5, corners: [.topRight, .bottomRight])
.shadow(color: Color.black.opacity(0.5), radius: 1, x: 1, y: 1)
}
}
.sheet(isPresented: $showImagePicker, onDismiss: loadImage){
if let key = dialogKey {
ImagePicker(key: key, uiImage: self.$uiImage)
.accentColor(Color.orange)
}
}
}
}
As you can see I attempted to change the Cancel button color to orange with the above code:
controller.navigationBar.topItem?.rightBarButtonItem?.tintColor = UIColor.orange
controller.navigationBar.topItem?.leftBarButtonItem?.tintColor = UIColor.orange
controller.navigationBar.backItem?.leftBarButtonItem?.tintColor = UIColor.orange
however it is still white.
Any idea how I can turn the Cancel button orange?