I see that IOS 16 has introduced the PhotosPicker view as an alternative to wrapping UIKit PHPickerViewController using UIViewControllerRepresentable. So I have modifed the Instafilter project to use PhotosPicker. A full solution would be to check the version of IOS and use the UIKit solution for IOS versions less than 16. But I was just curious if I could get PhotosPicker to function properly. So here is my ContentView.swift file:
//
// ContentView.swift
// Instafilter
//
// Created by Michael Knych on 11/2/22.
//
import CoreImage
import CoreImage.CIFilterBuiltins
import PhotosUI
import SwiftUI
//--------------------------------------------------------------
// Instafilter: Presents a button to select an image from the
// user's photo library and then display the selected image in the
// display area of the screen with a defualt filter of sepia
// and a defualt filter intensity of 0.7.
// The user can then select an alternative image filter from a presented
// list which will also show sliders that represent the input
// keys assocaited with the image filter when it's selected.
// There is also a save button that will save the ddisplayed filtered
// image into the user's photo library.
//--------------------------------------------------------------
struct ContentView: View {
@State var selectedItem: PhotosPickerItem?
@State var selectedPhotoData: Data?
@State private var image: Image?
@State private var filterIntensity = 0.7
@State private var filterRadius = 4.0
@State private var filterScale = 6.0
@State private var isEditing = false
// @State private var showingImagePicker = false
@State private var inputImage: UIImage?
@State private var processedImage: UIImage?
// This is a Core Image filter and a context
@State private var currentFilter: CIFilter = CIFilter.sepiaTone()
let context = CIContext()
@State private var showingFilterSheet = false
@State private var showingSaveError = false
var body: some View {
NavigationView {
VStack {
//------------------------------------------------------
// Create the image display area as a ZStack with an
// initial background set for the image display area
//------------------------------------------------------
ZStack {
Rectangle()
.fill(.secondary)
Text("Tap the Button below to select an image")
.foregroundColor(.white)
.font(.headline)
image?
.resizable()
.scaledToFit()
}
// .onTapGesture {
// showingImagePicker = true
// }
//---------------------------------------------------------
// Use the new PhotosPicker in ios 16 instead of the
// custom ImagePicker view
//----------------------------------------------------------
PhotosPicker(
selection: $selectedItem,
matching: .images
)
{
Label("Pick an image",systemImage: "photo")
.font(.headline)
}
.tint(.green)
.controlSize(.small)
.buttonStyle(.borderedProminent)
.onChange(of: selectedItem) { newValue in
Task {
if let data = try? await newValue?.loadTransferable(type: Data.self) {
selectedPhotoData = data
if let selectedPhotoData = selectedPhotoData {
inputImage = UIImage(data: selectedPhotoData)
}
}
}
}
//-------------------------------------------------------
// Show sliders for the input keys asscciated with the
// current filter selected
//-------------------------------------------------------
if currentFilter.inputKeys.contains(kCIInputIntensityKey) {
// Intesity slider HStack
HStack {
Text("Intensity")
VStack {
Slider(value: $filterIntensity,
onEditingChanged: { editing in
isEditing = editing
}
)
.onChange(of: filterIntensity) { _ in
applyProcessing()
}
Text("\(filterIntensity)")
.foregroundColor(isEditing ? .red : .blue)
}
} // end of Intensity slider HStack
.padding(.vertical)
}
if currentFilter.inputKeys.contains(kCIInputRadiusKey) {
// Radius slider HStack
HStack {
Text("Radius")
VStack {
Slider(value: $filterRadius, in: 0...200,
onEditingChanged: { editing in
isEditing = editing
}
)
.onChange(of: filterRadius) { _ in
applyProcessing()
}
Text("\(filterRadius)")
.foregroundColor(isEditing ? .red : .blue)
}
} // end of Radius slider HStack
.padding(.vertical)
}
if currentFilter.inputKeys.contains(kCIInputScaleKey) {
// Scale slider HStack
HStack {
Text("Scale")
VStack {
Slider(value: $filterScale, in: 0...10,
onEditingChanged: { editing in
isEditing = editing
}
)
.onChange(of: filterScale) { _ in
applyProcessing()
}
Text("\(filterScale)")
.foregroundColor(isEditing ? .red : .blue)
}
} // end of Scale slider HStack
.padding(.vertical)
}
HStack {
Button("Change filter") {
showingFilterSheet = true
}
Spacer()
Button("save", action: save)
.disabled(inputImage == nil)
}
} // end of main layout VStack
.padding([.horizontal, .bottom])
.navigationTitle("Instafilter")
//---------------------------------------------------------------------
// Run loadImage whenever the state of inputImage changes
//---------------------------------------------------------------------
.onChange(of: inputImage) { _ in loadImage() }
// present a sheet whenver showingImagePicker is true and run
// our custom ImagerPicker View
// .sheet(isPresented: $showingImagePicker) {
// ImagePicker(image: $inputImage)
// }
//---------------------------------------------------------------------
// Confirmation dialog to allow the user to select a filter to apply
// to the image
//---------------------------------------------------------------------
.confirmationDialog("Select a filter", isPresented: $showingFilterSheet) {
Group {
Button("Crystallize") { setFilter(CIFilter.crystallize()) }
Button("Edges") { setFilter(CIFilter.edges()) }
Button("Gaussian Blur") { setFilter(CIFilter.gaussianBlur()) }
Button("Pixellate") { setFilter(CIFilter.pixellate()) }
Button("Sepia Tone") { setFilter(CIFilter.sepiaTone()) }
Button("Unsharp Mask") { setFilter(CIFilter.unsharpMask()) }
Button("Vignette") { setFilter(CIFilter.vignette()) }
Button("Pointillize") { setFilter(CIFilter.pointillize()) }
Button("Bloom") { setFilter(CIFilter.bloom()) }
Button("Noir") { setFilter(CIFilter.photoEffectNoir()) }
}
Group {
Button("Cancel", role: .cancel) { }
}
}
.alert("Error saving image", isPresented: $showingSaveError) {
Button("OK") { }
} message: {
Text("Sorry there was an error saving your image. Please check that you have allowed permissions for the app to save images to your photo library")
}
} // end of Navigation View
}
//--------------------------------------------------------------
// Set the inputImage of UIIKMage type into the image for the
// CI IMage filter (currentFilter) and then call the applyProccesing()
// method that will apply filtering to the image that has been
// set in currentFilter
//---------------------------------------------------------------
func loadImage() {
// Check to see if we actually have an image in inputImage
guard let inputImage = inputImage else { return }
// If so, convert the UIImage into a CIImage
// set the CIImage in the context
// run the applyProcessing to apply the filter(s)
let beginImage = CIImage(image: inputImage)
currentFilter.setValue(beginImage, forKey: kCIInputImageKey)
applyProcessing()
}
//--------------------------------------------------------------
// Will process filters to whatever image is imported by
// setting it in curretFlter.setValue with the
// forkey: kCIInputImageKey
// (see the code that calls this method)
//--------------------------------------------------------------
func applyProcessing() {
// Check for various filter keys and set the appropriate key value for
// the filter set in the currentFilter
let inputKeys = currentFilter.inputKeys
print("\(inputKeys)")
if inputKeys.contains(kCIInputIntensityKey) {
currentFilter.setValue(filterIntensity, forKey: kCIInputIntensityKey)
}
if inputKeys.contains(kCIInputRadiusKey) {
currentFilter.setValue(filterRadius * 200, forKey: kCIInputRadiusKey)
}
if inputKeys.contains(kCIInputScaleKey) {
currentFilter.setValue(filterScale * 10, forKey: kCIInputScaleKey)
}
guard let outputImage = currentFilter.outputImage else { return }
if let cgimg = context.createCGImage(outputImage, from: outputImage.extent) {
let uiImage = UIImage(cgImage: cgimg)
image = Image(uiImage: uiImage)
processedImage = uiImage
}
}
//------------------------------------------------------------------------
// Set the CIFilter past into this function to the curretFilter state
// property and then run the loadIMage method to process and filter
// the image selected by the user with the photo picker.
//------------------------------------------------------------------------
func setFilter(_ filter: CIFilter) {
currentFilter = filter
loadImage()
}
func save() {
guard let processedImage = processedImage else { return }
// create an instance of ImageSaver class
let imageSaver = ImageSaver()
// specify the actions for the ImageSaver class error handler
// colures.
imageSaver.successHandler = {
print("Image saved")
}
imageSaver.errorHanddler = {
showingSaveError = true
print("Error saving image: \($0.localizedDescription)")
}
imageSaver.writeToPhotoAlbum(image: processedImage)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}