114 lines
4.4 KiB
Swift
114 lines
4.4 KiB
Swift
import SwiftUI
|
|
import UIKit
|
|
import Photos
|
|
|
|
struct ImageViewerView: View {
|
|
let imageData: Data
|
|
@State private var scale: CGFloat = 1.0
|
|
@State private var saved = false
|
|
@State private var saveError: String?
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
GeometryReader { geo in
|
|
if let uiImage = UIImage(data: imageData) {
|
|
Image(uiImage: uiImage)
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fit)
|
|
.scaleEffect(scale)
|
|
.gesture(
|
|
MagnifyGesture()
|
|
.onChanged { value in
|
|
scale = value.magnification
|
|
}
|
|
.onEnded { _ in
|
|
withAnimation {
|
|
scale = max(1.0, min(scale, 5.0))
|
|
}
|
|
}
|
|
)
|
|
.onTapGesture(count: 2) {
|
|
withAnimation {
|
|
scale = scale > 1 ? 1 : 2
|
|
}
|
|
}
|
|
.frame(width: geo.size.width, height: geo.size.height)
|
|
}
|
|
}
|
|
.overlay(alignment: .bottom) {
|
|
if let error = saveError {
|
|
Text(error)
|
|
.font(.caption)
|
|
.foregroundStyle(.white)
|
|
.padding(8)
|
|
.background(Capsule().fill(.red.opacity(0.8)))
|
|
.padding(.bottom, 40)
|
|
.transition(.move(edge: .bottom).combined(with: .opacity))
|
|
}
|
|
}
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarLeading) {
|
|
Button {
|
|
dismiss()
|
|
} label: {
|
|
Image(systemName: "xmark")
|
|
.foregroundStyle(.white)
|
|
}
|
|
}
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
HStack(spacing: 16) {
|
|
// Share
|
|
if let uiImage = UIImage(data: imageData) {
|
|
ShareLink(item: Image(uiImage: uiImage), preview: SharePreview("Image", image: Image(uiImage: uiImage))) {
|
|
Image(systemName: "square.and.arrow.up")
|
|
.foregroundStyle(.white)
|
|
}
|
|
}
|
|
|
|
// Save to Photos
|
|
Button {
|
|
saveToPhotos()
|
|
} label: {
|
|
Image(systemName: saved ? "checkmark.circle.fill" : "arrow.down.to.line")
|
|
.foregroundStyle(saved ? .green : .white)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.toolbarBackground(.hidden, for: .navigationBar)
|
|
.background(.black)
|
|
}
|
|
}
|
|
|
|
private func saveToPhotos() {
|
|
guard let uiImage = UIImage(data: imageData) else {
|
|
withAnimation { saveError = "Invalid image data" }
|
|
return
|
|
}
|
|
|
|
PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in
|
|
DispatchQueue.main.async {
|
|
switch status {
|
|
case .authorized, .limited:
|
|
PHPhotoLibrary.shared().performChanges {
|
|
PHAssetChangeRequest.creationRequestForAsset(from: uiImage)
|
|
} completionHandler: { success, error in
|
|
DispatchQueue.main.async {
|
|
if success {
|
|
withAnimation { saved = true; saveError = nil }
|
|
} else {
|
|
withAnimation { saveError = error?.localizedDescription ?? "Save failed" }
|
|
}
|
|
}
|
|
}
|
|
case .denied, .restricted:
|
|
withAnimation { saveError = "Photo library access denied. Check Settings." }
|
|
default:
|
|
withAnimation { saveError = "Photo library access required" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|