Swiftui:如何快照视图然后共享?
Swiftui: How to snapshot a view then share?
我找到 this code for taking a snapshot of a view in SwiftUI, and also found this gist 如何在 SwiftUI 中调出 UIActivityController。它工作正常,但我遇到的最大问题是当你点击共享时 UIActivityController 是空白的,如果你再次点击共享它会按预期工作但我无法弄清楚为什么它第一次不起作用?如果我更改为静态图像或文本来共享,它会按预期工作吗?有什么想法吗?
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")
}
}
}
添加 [shareCardAsImage]
以便在 sheet
:
中捕获当前值
.sheet(item: $activeSheet) { [shareCardAsImage] sheet in
这是必要的,因为您的 item
没有明确捕获它,这通常是 item
的用法。您还可以通过在 ActiveSheet
上添加关联值来解决此问题,该值将图像存储在 item
.
我找到 this code for taking a snapshot of a view in SwiftUI, and also found this gist 如何在 SwiftUI 中调出 UIActivityController。它工作正常,但我遇到的最大问题是当你点击共享时 UIActivityController 是空白的,如果你再次点击共享它会按预期工作但我无法弄清楚为什么它第一次不起作用?如果我更改为静态图像或文本来共享,它会按预期工作吗?有什么想法吗?
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")
}
}
}
添加 [shareCardAsImage]
以便在 sheet
:
.sheet(item: $activeSheet) { [shareCardAsImage] sheet in
这是必要的,因为您的 item
没有明确捕获它,这通常是 item
的用法。您还可以通过在 ActiveSheet
上添加关联值来解决此问题,该值将图像存储在 item
.