I want to add images from phone's photo library into a collage layout that I made. First I made the collage layout as a separate view in SwiftUI called CollageLayoutOne.
import SwiftUI
struct CollageLayoutOne: View {
var uiImageOne: UIImage
var uiImageTwo: UIImage
var uiImageThree: UIImage
var body: some View {
Rectangle()
.fill(Color.gray)
.aspectRatio(1.0, contentMode: .fit)
.overlay {
HStack {
Rectangle()
.fill(Color.gray)
.overlay {
Image(uiImage: uiImageOne)
.resizable()
.aspectRatio(contentMode: .fill)
}
.clipped()
VStack {
Rectangle()
.fill(Color.gray)
.overlay {
Image(uiImage: uiImageTwo)
.resizable()
.aspectRatio(contentMode: .fill)
}
.clipped()
Rectangle()
.fill(Color.gray)
.overlay {
Image(uiImage: uiImageThree)
.resizable()
.aspectRatio(contentMode: .fill)
}
.clipped()
}
}
.padding()
}
}
}
Then I have a separate view (PageView) where I want to show the CollageLayoutOne view and it also hosts the button to get to the image library.
struct PageView: View {
#State private var photoPickerIsPresented = false
#State var pickerResult: [UIImage] = []
var body: some View {
NavigationView {
ScrollView {
if pickerResult.isEmpty {
} else {
CollageLayoutOne(uiImageOne: pickerResult[0], uiImageTwo: pickerResult[1], uiImageThree: pickerResult[2])
}
}
.edgesIgnoringSafeArea(.bottom)
.navigationBarTitle("Select Photo", displayMode: .inline)
.navigationBarItems(trailing: selectPhotoButton)
.sheet(isPresented: $photoPickerIsPresented) {
PhotoPicker(pickerResult: $pickerResult,
isPresented: $photoPickerIsPresented)
}
}
}
#ViewBuilder
private var selectPhotoButton: some View {
Button(action: {
photoPickerIsPresented = true
}, label: {
Label("Select", systemImage: "photo")
})
}
}
My problem is that for some unknown reason the app crashes every time I select the photos and try to add them. If I do pickerResult[0] for all three it works just fine, but displays only the first selected photo on all 3 spots. Also if I start with all 3 as pickerResult[0] and then change them to [0], [1], [2] while the preview is running it doesn't crash and displays correctly.
I'm just starting with Swift and SwiftUI, so excuse me if it's some elementary mistake. Below I am also adding my code for PhotoPicker that I got from an article I found.
PhotoPicker.swift:
import SwiftUI
import PhotosUI
struct PhotoPicker: UIViewControllerRepresentable {
#Binding var pickerResult: [UIImage]
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> some UIViewController {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.filter = .images // filter only to images
if #available(iOS 15, *) {
configuration.selection = .ordered //number selection
}
configuration.selectionLimit = 3 // ignore limit
let photoPickerViewController = PHPickerViewController(configuration: configuration)
photoPickerViewController.delegate = context.coordinator
return photoPickerViewController
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: PHPickerViewControllerDelegate {
private let parent: PhotoPicker
init(_ parent: PhotoPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.pickerResult.removeAll()
for image in results {
if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
image.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] newImage, error in
if let error = error {
print("Can't load image \(error.localizedDescription)")
} else if let image = newImage as? UIImage {
self?.parent.pickerResult.append(image)
}
}
} else {
print("Can't load asset")
}
}
parent.isPresented = false
}
}
}
image.itemProvider.loadObject is an asynchronous function, and it loads images one by one.
When the first image is processed, you add it to pickerResult and your pickerResult.isEmpty check becomes false, but your array contains only one item so far.
The safe thing to do here is to check the count:
if pickerResult.count == 3 {
CollageLayoutOne(uiImageOne: pickerResult[0], uiImageTwo: pickerResult[1], uiImageThree: pickerResult[2])
}
Also, in such cases, it's a good idea to wait until all asynchronous requests are complete before updating the UI, for example, like this:
var processedResults = [UIImage]()
var leftToLoad = results.count
let checkFinished = { [weak self] in
leftToLoad -= 1
if leftToLoad == 0 {
self?.parent.pickerResult = processedResults
self?.parent.isPresented = false
}
}
for image in results {
if image.itemProvider.canLoadObject(ofClass: UIImage.self) {
image.itemProvider.loadObject(ofClass: UIImage.self) { newImage, error in
if let error = error {
print("Can't load image \(error.localizedDescription)")
} else if let image = newImage as? UIImage {
processedResults.append(image)
}
checkFinished()
}
} else {
print("Can't load asset")
checkFinished()
}
}
Related
I am new to SwiftUI. I want to make an app that has an image picker.
I found this article: https://ishtiz.com/swiftui/image-picker-in-swiftui
It says:
To implement an image picker in SwiftUI, you can use the ImagePicker struct provided by the SwiftUI framework. This struct has a pickImage() method that presents the image picker to the user and returns the selected image as a UIImage object.
Providing this example code:
struct ContentView: View {
#State private var image: UIImage?
var body: some View {
VStack {
if image != nil {
Image(uiImage: image!)
.resizable()
.scaledToFit()
}
Button("Select Image") {
self.image = ImagePicker.pickImage()
}
}
}
}
I added the code to my project but it doesn’t build:
Cannot find 'ImagePicker' in scope
Do I need to import something?
try import PhotosUI
For iOS Versions lower than 16.0
you can refers tutorial
For iOS 16.0 + you can use PhotoPicker
import PhotosUI
import SwiftUI
#available(iOS 16.0, *)
struct PhotosPickerDemo: View {
#State private var selectedItem: PhotosPickerItem? = nil
#State private var selectedImageData: Data? = nil
var body: some View {
PhotosPicker(
selection: $selectedItem,
matching: .images,
photoLibrary: .shared()) {
Text("Select a photo")
}
.onChange(of: selectedItem) { newItem in
Task {
// Retrieve selected asset in the form of Data
if let data = try? await newItem?.loadTransferable(type: Data.self) {
selectedImageData = data
}
}
}
if let selectedImageData,
let uiImage = UIImage(data: selectedImageData) {
Image(uiImage: uiImage)
.resizable()
.scaledToFit()
.frame(width: 250, height: 250)
}
}
}
My UIView doesn't update after I change its properties in the updateUIView function. To test it, I click the button in the VStack which calls generateBarcodeData and changes the state of barcode.
I monitored the updateUIView function in BarCodeView and it definitely is getting called, however I don't see any changes on the simulator.
import SwiftUI
struct MainView: View {
let screenSize = UIScreen.main.bounds
let titleOffset = UIScreen.main.bounds.height/25
let mainModalOffset = UIScreen.main.bounds.height/10
#State private var barcode: String = "&723852390"
var body: some View {
ZStack() {
Color.blue.ignoresSafeArea()
VStack() {
Text("-|||||-")
.font(.system(.title, design: .rounded))
.fontWeight(.semibold)
.foregroundColor(Color.yellow)
.offset(y: titleOffset)
Spacer()
}
VStack() {
BarCodeView(barcode: $barcode)
.frame(height: screenSize.height/2.5)
.padding()
Button(action: {
generateBarcodeData()
})
{
Text("Reset Barcode")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(10.0)
.padding(.bottom, 20)
}
}
.padding()
.padding(.bottom, 150)
.frame(height: screenSize.height)
.background(Color.white)
.offset(y: mainModalOffset)
}
}
func generateBarcodeData() {
// let src128API = Src128API(username: self.username, password: self.password)
// src128API.getBarcode() { (barcodeData) in
// barcode = barcodeData
// print(barcodeData)
// }
let min: UInt32 = 100_000_000
let max: UInt32 = 999_999_999
let i = min + arc4random_uniform(max - min + 1)
barcode = String(i)
print(barcode)
}
}
extension UIImage {
convenience init?(barcode: String) {
let data = barcode.data(using: .ascii)
guard let filter = CIFilter(name: "CICode128BarcodeGenerator") else {
return nil
}
filter.setValue(data, forKey: "inputMessage")
guard let ciImage = filter.outputImage else {
return nil
}
self.init(ciImage: ciImage)
}
}
struct BarCodeView: UIViewRepresentable {
#Binding var barcode: String
func makeUIView(context: Context) -> UIImageView {
let imageView = UIImageView()
return imageView
}
func updateUIView(_ uiView: UIImageView, context: Context) {
uiView.image = UIImage(barcode: barcode)
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
I built the code to my iPhone and it worked on there. I guess it was just broken on the simulator.
I am trying to display two columns of images in a LazyVStack embedded in a scroll view however the the second row of images partially overlaps the row above. I'm not sure if this is an issue with the LazyVStack itself or an issue with the Photo.swift view.
The output looks like this
The two view files
ContentView.swift
struct ContentView: View {
#State private var image: Image?
#State private var showingCustomCamera = false
#State private var inputImage: UIImage?
#State private var photos: [UIImage] = []
func addImageToArray() {
guard let inputImage = inputImage else { return }
image = Image(uiImage: inputImage)
let ciImage = CIImage(cgImage: inputImage.cgImage!)
let options = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: options)!
let faces = faceDetector.features(in: ciImage)
if let face = faces.first as? CIFaceFeature {
print("Found face at \(face.bounds)")
print(face.faceAngle)
print(face.hasSmile)
print(face.leftEyeClosed)
print(face.rightEyeClosed)
if face.leftEyeClosed {
print("Left Eye Closed \(face.leftEyePosition)")
}
if face.rightEyeClosed {
print("Right Eye Closed \(face.rightEyePosition)")
}
if face.hasSmile {
print("Person is smiling \(face.mouthPosition)")
}
}
photos.append(inputImage)
}
let columns = [
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20)
]
var body: some View {
NavigationView {
VStack{
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
AddPhoto(showCamera: $showingCustomCamera)
ForEach(photos, id: \.self) { photo in
PassportPhoto(img: photo)
}
}
.padding()
}
HStack {
Button(action: {
//
}, label: {
Image(systemName: "printer.fill.and.paper.fill")
Text("Print")
})
.padding()
.foregroundColor(.primary)
Button(action: {
//
}, label: {
Image(systemName: "externaldrive.fill.badge.icloud")
Text("Digital Upload")
})
.padding()
.foregroundColor(.primary)
}
}
.sheet(isPresented: $showingCustomCamera, onDismiss: addImageToArray) {
CustomCameraView(image: self.$inputImage)
}
.navigationTitle("Add Photos")
}
}
}
Photo.swift
struct Photo: View {
var img: UIImage
#State private var overlay: Bool = false
var body: some View {
GeometryReader { geometry in
VStack {
ZStack(alignment: .top) {
Image(uiImage: img)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: geometry.size.width, height: geometry.size.width * 1.29, alignment: .top)
.clipped()
.cornerRadius(10)
.onTapGesture {
self.overlay.toggle()
}
if overlay {
Template()
}
}
}
}
}
}
Anyone have any idea? I feel like I'm missing something obvious.
CustomCameraView.swift (as requested)
import SwiftUI
import AVFoundation
struct CustomCameraView: View {
#Binding var image: UIImage?
#State var didTapCapture: Bool = false
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack(alignment: .center) {
CustomCameraRepresentable(image: self.$image, didTapCapture: $didTapCapture)
.overlay(Template(),alignment: .center)
.overlay(
CaptureButtonView().onTapGesture {
self.didTapCapture = true
}
, alignment: .bottom)
.overlay(
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Image(systemName: "multiply")
.scaleEffect(2)
.padding(20)
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
})
.foregroundColor(.white)
.padding()
, alignment: .topTrailing)
}
}
}
struct CustomCameraRepresentable: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
#Binding var image: UIImage?
#Binding var didTapCapture: Bool
func makeUIViewController(context: Context) -> CustomCameraController {
let controller = CustomCameraController()
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ cameraViewController: CustomCameraController, context: Context) {
if(self.didTapCapture) {
cameraViewController.didTapRecord()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UINavigationControllerDelegate, AVCapturePhotoCaptureDelegate {
let parent: CustomCameraRepresentable
init(_ parent: CustomCameraRepresentable) {
self.parent = parent
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
parent.didTapCapture = false
if let imageData = photo.fileDataRepresentation() {
parent.image = UIImage(data: imageData)
}
parent.presentationMode.wrappedValue.dismiss()
}
}
}
class CustomCameraController: UIViewController {
var image: UIImage?
var captureSession = AVCaptureSession()
var backCamera: AVCaptureDevice?
var frontCamera: AVCaptureDevice?
var currentCamera: AVCaptureDevice?
var photoOutput: AVCapturePhotoOutput?
var cameraPreviewLayer: AVCaptureVideoPreviewLayer?
//DELEGATE
var delegate: AVCapturePhotoCaptureDelegate?
func didTapRecord() {
let settings = AVCapturePhotoSettings()
photoOutput?.capturePhoto(with: settings, delegate: delegate!)
}
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func setup() {
setupCaptureSession()
setupDevice()
setupInputOutput()
setupPreviewLayer()
startRunningCaptureSession()
}
func setupCaptureSession() {
captureSession.sessionPreset = AVCaptureSession.Preset.photo
}
func setupDevice() {
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera],
mediaType: AVMediaType.video,
position: AVCaptureDevice.Position.unspecified)
for device in deviceDiscoverySession.devices {
switch device.position {
case AVCaptureDevice.Position.front:
self.frontCamera = device
case AVCaptureDevice.Position.back:
self.backCamera = device
default:
break
}
}
self.currentCamera = self.backCamera
}
func setupInputOutput() {
do {
let captureDeviceInput = try AVCaptureDeviceInput(device: currentCamera!)
captureSession.addInput(captureDeviceInput)
photoOutput = AVCapturePhotoOutput()
photoOutput?.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])], completionHandler: nil)
captureSession.addOutput(photoOutput!)
} catch {
print(error)
}
}
func setupPreviewLayer()
{
let rect = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.width * 1.29)
self.cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
self.cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
self.cameraPreviewLayer?.frame = rect
self.view.layer.insertSublayer(cameraPreviewLayer!, at: 0)
}
func startRunningCaptureSession(){
captureSession.startRunning()
}
}
struct CaptureButtonView: View {
#State private var animationAmount: CGFloat = 1
var body: some View {
Image(systemName: "camera").font(.largeTitle)
.padding(30)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
.overlay(
Circle()
.stroke(Color.red)
.scaleEffect(animationAmount)
.opacity(Double(2 - animationAmount))
.animation(Animation.easeOut(duration: 1)
.repeatForever(autoreverses: false))
)
.padding(.bottom)
.onAppear
{
self.animationAmount = 2
}
}
}
You shouldn’t use GeometryReader from within the ScrollView, it will create all sort of mess for you. Instead define it at top level just under VStack, and pass the proxy down to Photo view to set Frame.
Check the code below-:
import SwiftUI
struct Test1: View {
#State private var image: Image?
#State private var showingCustomCamera = false
#State private var inputImage: UIImage?
#State private var photos: [UIImage] = []
func addImageToArray() {
guard let inputImage = inputImage else { return }
image = Image(uiImage: inputImage)
let ciImage = CIImage(cgImage: inputImage.cgImage!)
let options = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: options)!
let faces = faceDetector.features(in: ciImage)
if let face = faces.first as? CIFaceFeature {
print("Found face at \(face.bounds)")
print(face.faceAngle)
print(face.hasSmile)
print(face.leftEyeClosed)
print(face.rightEyeClosed)
if face.leftEyeClosed {
print("Left Eye Closed \(face.leftEyePosition)")
}
if face.rightEyeClosed {
print("Right Eye Closed \(face.rightEyePosition)")
}
if face.hasSmile {
print("Person is smiling \(face.mouthPosition)")
}
}
photos.append(inputImage)
}
let columns =
[GridItem(.flexible(),spacing: 20),
GridItem(.flexible(),spacing: 20)]
var body: some View {
NavigationView {
VStack{
GeometryReader { geometry in
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
// AddPhoto(showCamera: $showingCustomCamera) // Uncomment in your case
ForEach(0..<50, id: \.self) { photo in
Photo(img: "ABC", proxy: geometry) // Pass photo as you were doing
}
}
.padding()
}
}
HStack {
Button(action: {
//
}, label: {
Image(systemName: "printer.fill.and.paper.fill")
Text("Print")
})
.padding()
.foregroundColor(.primary)
Button(action: {
//
}, label: {
Image(systemName: "externaldrive.fill.badge.icloud")
Text("Digital Upload")
})
.padding()
.foregroundColor(.primary)
}
}
.sheet(isPresented: $showingCustomCamera, onDismiss: addImageToArray) {
// CustomCameraView(image: self.$inputImage)
}
.navigationTitle("Add Photos")
}
}
}
struct Photo: View {
var img: String
var proxy:GeometryProxy
#State private var overlay: Bool = false
var body: some View {
// GeometryReader { geometry in
VStack {
ZStack(alignment: .top) {
Image(img)
.resizable()
.aspectRatio(contentMode: .fill)
// .frame(width: 170, height: 200)
.frame(width: proxy.size.width * 0.4, height: proxy.size.width * 0.5, alignment: .top)
.clipped()
.cornerRadius(10)
.onTapGesture {
self.overlay.toggle()
}
if overlay {
// Template()
}
}
}
//}
}
}
I have a very odd issue. I have a list app that crashes when I delete a list item that I just viewed. I can delete an item that is different than the one I just viewed without the crash. The crash error is:
Fatal error: Unexpectedly found nil while unwrapping an Optional value: file /Users/XXX/Documents/Xcode Projects Playground/Test-Camera-CloudKit/Test-Camera-CloudKit/DetailView.swift, line 20
Line 20 of the DetailView.swift file is the line that displays the image/photo [Image(uiImage: UIImage(data: myItem.photo!) ?? UIImage(named: "gray_icon")!)]. Below are the files from my stripped down app to try to run this issue to ground. I am using CoreData and CloudKit.
ContentView.swift:
import SwiftUI
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: Item.entity(), sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)]) var items: FetchedResults<Item>
#State private var showingAddScreen = false
var body: some View {
NavigationView {
List {
ForEach(items, id: \.self) { item in
NavigationLink(destination: DetailView(myItem: item)) {
HStack {
Text(item.name ?? "Unknown name")
}
}
}.onDelete(perform: delete)
}
.navigationBarTitle("Items")
.navigationBarItems(trailing:
Button(action: {
self.showingAddScreen.toggle()
}) {
Image(systemName: "plus")
}
)
.sheet(isPresented: $showingAddScreen) {
AddItemView().environment(\.managedObjectContext, self.moc)
}
}
}
func delete(at offsets: IndexSet) {
for index in offsets {
let item = items[index]
moc.delete(item)
}
do {
try moc.save()
} catch {
print("Error deleting objects")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
AddItemView.swift:
import SwiftUI
struct AddItemView: View {
#Environment(\.managedObjectContext) var moc
#Environment(\.presentationMode) var presentationMode
#State private var image : Data = .init(count: 0)
#State private var name = ""
#State private var show = false
var body: some View {
NavigationView {
Form {
Section {
TextField("Name of item", text: $name)
}
Section {
VStack {
Button(action: {self.show = true}) {
HStack {
Image(systemName: "camera")
}
}
Image(uiImage: UIImage(data: self.image) ?? UIImage(named: "gray_icon")!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 300, alignment: Alignment.center)
.clipped()
}
.sheet(isPresented: self.$show, content: {
ImagePicker(show: self.$show, image: self.$image)
})
}
Section {
Button("Save") {
let newItem = Item(context: self.moc)
newItem.name = self.name
newItem.photo = self.image
try? self.moc.save()
self.presentationMode.wrappedValue.dismiss()
}
}
}
.navigationBarTitle("Add Item")
}
}
}
struct AddItemView_Previews: PreviewProvider {
static var previews: some View {
AddItemView()
}
}
DetailView.swift
import SwiftUI
struct DetailView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var myItem: Item
var body: some View {
VStack {
Text(myItem.name ?? "Unknown")
Image(uiImage: UIImage(data: myItem.photo!) ?? UIImage(named: "gray_icon")!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 300, alignment: Alignment.center)
.clipped()
}
.navigationBarTitle(Text(myItem.name ?? "Unknown"), displayMode: .inline)
}
}
struct DetailView_Previews: PreviewProvider {
static let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
static var previews: some View {
let item = Item(context: moc)
item.name = "Test"
return NavigationView {
DetailView(myItem: item)
}
}
}
ImagePicker.swift
import SwiftUI
import Combine
struct ImagePicker : UIViewControllerRepresentable {
#Binding var show : Bool
#Binding var image : Data
func makeCoordinator() -> ImagePicker.Coordinator {
return ImagePicker.Coordinator(child1: self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.sourceType = .photoLibrary
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var child : ImagePicker
init(child1: ImagePicker) {
child = child1
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.child.show.toggle()
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let image = info[.originalImage]as! UIImage
let data = image.jpegData(compressionQuality: 0.45)
self.child.image = data!
self.child.show.toggle()
}
}
}
I am really struggling with how does the app have an error in a view that is not being shown when an item is deleted. The list items does get deleted and removed from CloudKit. The delete operation works. The crash and error happens whether the coredata attribute for the photo has a photo or not. In other words, it has the error even when the item does have a photo and is not nil. Where am I going wrong? How do I get it to allow me to delete an item that I just viewed in the DetailView without a nil error? Any help is greatly appreciated. Thanks.
I have data loaded into an HStack that is in a Scroll View in SwiftUI. Right now I have it coded where a user can tap on one of those items and have it selected. I'd like for the 0th item to already be selected upon load.
import SwiftUI
import Combine
import Contentful
struct moviesView : View {
#ObservedObject var fetcher = MovieFetcher()
#State var selectMovie: Movie? = nil
#Binding var show: Bool
var body: some View {
HStack(alignment: .bottom) {
if show {
ScrollView(.horizontal) {
Spacer()
HStack(alignment: .bottom, spacing: 30) {
ForEach(fetcher.movies, id: \.self) { item in
selectableRow(movie: item, selectMovie: self.$selectMovie)
}
.onAppear() {
self.selectMovie = self.movies.count > 0 ? self.movies.first! : nil
}
}
.frame(minWidth: 0, maxWidth: .infinity)
}
.padding(.leading, 46)
.padding(.bottom, 26)
}
}
}
}
struct selectableRow : View {
var movie: Movie
#Binding var selectedMovie: Movie?
#State var initialImage = UIImage()
var body: some View {
ZStack(alignment: .center) {
if movie == selectedMovie {
Image("")
.resizable()
.frame(width: 187, height: 254)
.overlay(
RoundedRectangle(cornerRadius: 13)
Image(uiImage: initialImage)
.resizable()
.cornerRadius(13.0)
.frame(width: 182, height: 249)
.onAppear {
let urlString = "\(urlBase)\(self.movie.movieId).png?"
guard let url = URL(string: self.urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
guard let image = UIImage(data: data) else { return }
RunLoop.main.perform {
self.initialImage = image
}
}.resume()
}
} else {
Image(uiImage: initialImage)
.resizable()
.cornerRadius(13.0)
.frame(width: 135, height: 179)
.onAppear {
let urlString = "\(urlBase)\(self.movie.movieId).png?"
guard let url = URL(string: self.urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
guard let image = UIImage(data: data) else { return }
RunLoop.main.perform {
self.initialImage = image
}
}.resume()
}
}
}
.onTapGesture {
self.selectedMovie = self.movie
}
}
}
EDIT:
I've added the below suggestion but it's still now working properly. Maybe it's where I added the .onAppear?
So when I launch my app I see the 0th item is selected but when I tap on any item the view just reloads but the 0th item always stays selected.
Additional issue:
Also, my #ObservedObject var fetcher = MovieFetcher() in moviesView is called repeatedly.
Since you haven't given the full working code, I wasn't able to reproduce the issue you've mentioned. However, I'd suggest you move the .onAppear from ForEach to the HStack (see code below).
I couldn't reproduce the issue you specified.
var body: some View {
HStack(alignment: .bottom) {
if show {
ScrollView(.horizontal) {
Spacer()
HStack(alignment: .bottom, spacing: 30) {
ForEach(fetcher.movies, id: \.self) { item in
selectableRow(movie: item, selectedMovie: self.$selectMovie)
}
}
.frame(minWidth: 0, maxWidth: .infinity)
}
.padding(.leading, 46)
.padding(.bottom, 26)
.onAppear() {
self.selectMovie = self.fetcher.movies.count > 0 ? self.fetcher.movies.first! : nil
}
}
}
}
In the struct moviesView, use the below code to auto select the first movie.
.onAppear() {
self.selectMovie = self.movies.count > 0 ? self.movies.first! : nil
}
Let me know if you have any other questions.