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" } } } } } }