loading image in swiftUI causing nil while unwrapping - swiftui

I'm getting the following error:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
In my ProfileView() im using import URLImage library.
URLImage(URL(string: self.profileViewModel.profileURL)!, placeholder: Image(systemName: "circle"),
content: {
$0.image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 130, height: 130)
.cornerRadius(15)
.overlay(
Image(systemName: "plus.circle.fill")
.foregroundColor(.gray)
.font(.largeTitle)
,alignment: .bottomTrailing)
.onTapGesture {
self.profileViewModel.showImagePicker = true
}
}
)
In my ProfileView() im using .onAppear(perform: loadUserData)
This works good, brings back the correct data for the user and displays it as expected.
func LoadAUser(userId: String) {
Firestore.firestore().collection("users").document(userId).getDocument{(snapshot, error) in
guard let snap = snapshot else {
print("error fetching data")
return
}
let dict = snap.data()
guard let decodedUser = try? User.init(fromDictionary: dict!) else { return }
print("decoded user - load user - \(decodedUser)")
self.bio = decodedUser.bio
self.occupation = decodedUser.occupation
self.city = decodedUser.city
self.profileURL = decodedUser.profileURL
let storageRoot = Storage.storage().reference(forURL: "gs://myapp.appspot.com").child("avatar").child(userId)
storageRoot.downloadURL{(url, error) in
if error != nil {
print(error!.localizedDescription)
return
}
let profileDownload = url?.absoluteString
self.profileURL = profileDownload!
}
print("profileURL - \(self.profileURL)")
}
}
The storage has an image and i can retrieve it as the log shows print("profileURL - \(self.profileURL)") everything is good here
I just can't seem to display the profile image in the ProfileView()
the function is called in the profile view like so in .onAppear
func loadUserData() {
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.profileViewModel.LoadAUser(userId: Auth.auth().currentUser!.uid)
}
}
I had tried adding a delay:
DispatchQueue.main.asyncAfter(deadline: .now() + 10)
Just the image won't load and im not sure why it says found nil when every thing else is loaded but when I add this URLImage to bring the image back it says nil, even tho the log says it has the correct URL.
UPDATE
I have a class ProfileViewModel: ObservableObject
Which has
#Published var profileURL: String = ""
In loadAUser function. I set it:
self.profileURL = profileDownload!

I can't see an error inside your posted code, but there are some things which you didn't post about your class:
You seem to have class (the profileViewModel) in which you have to save your image to a variable which was at the top of your class declared like this: #Published var image ...
If you say you are able to retrieve all the data you need (including the image) with .onAppear(perform: loadUserData) then you just have to save your image to the variable after fetching it inside your ViewModel and alle Views with access to the #ObservedObject profileViewModel have access to profileViewModel.image.

A work around, I got the value from the sessionStore
URLImage(URL(string: self.session.userInSession!.profileURL)!,

Related

Swiftui image not getting rendered in sharesheet - unless you click twice

I create a view that will become a snapshot to send to the Share Sheet. Unfortunately, I get a nil image the first time I click share. The second time, the snapshot image shows up fine.
struct AffirmationSharingView: View {
//saving this view to send to shareSheet
var viewToShare: some View {
ZStack{
Image("night4")
.resizable()
Text("affirmation here")
.foregroundColor(.white)
}
}
#State private var showShareSheet = false
#State var myImage: UIImage! = UIImage(named: "test")
var body: some View {
GeometryReader{gp in
ZStack{
Image("night4")
.resizable()
.frame(width: gp.size.width*0.9, height: gp.size.height*0.5 , alignment: .top)
Text("affirmation here")
Spacer()
HStack{
VStack{
Image("wigshareIcon")
.resizable()
.aspectRatio(1, contentMode:.fit)
.frame(width: gp.size.width*0.2, height: 60, alignment: .top)
Text("Share To Other Social Media")
}
.onTapGesture {
//save the image/affirmation combo to a UIImage to be sent to the share sheet
myImage = viewToShare.snapshot()
self.showShareSheet = true
}
}
.padding()
}
}
}
.onAppear(perform: getImage)
.sheet(isPresented: $showShareSheet, content: {
// let myImage3 = viewToShare.snapshot()
ShareView(activityItems: ["Rest Rise Grow App!",myImage]) //myImage!]) //[myImage as! Any]) //[data])
}
func getImage(){
self.myImage = viewToShare.snapshot()
if self.myImage != nil {
print("1sharesheet has some value")
print("1sharesheet equals \(myImage)")
} else {
print("Did not set 1screenshot")
}
}
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
Here are the logs:
2021-12-27 20:20:44.389453-0500 Rest Rise Grow[1672:423870] [Snapshotting] View (0x102168800, _TtGC7SwiftUI14_UIHostingViewGVS_15ModifiedContentGS1_GVS_6ZStackGVS_9TupleViewTGS1_VS_14LinearGradientVS_12_FrameLayout_GS1_VS_5ImageGVS_16_OverlayModifierGVS_10_ShapeViewGVS_13_StrokedShapeVVS_16RoundedRectangle6_Inset_VS_5Color___GVS_19_ConditionalContentVS_4TextS14_____GVS_11_ClipEffectS10___GVS_19_BackgroundModifierS12____) drawing with afterScreenUpdates:YES inside CoreAnimation commit is not supported.
2021-12-27 20:20:45.865260-0500 Rest Rise Grow[1672:424205] Metal API Validation Enabled
sharesheet has some value
sharesheet equals Optional(<UIImage:0x280e48360 anonymous {786, 831}>)
Rest_Rise_Grow/SettingsViewController.swift:833: Fatal error: Unexpectedly found nil while unwrapping an Optional value
2021-12-27 20:20:46.296762-0500 Rest Rise Grow[1672:423870] Rest_Rise_Grow/SettingsViewController.swift:833: Fatal error: Unexpectedly found nil while unwrapping an Optional value

Using .redacted & AsyncImage together

I would like to use .redacted on the entire view until the image is successfully loaded into AsyncImage.. Currently I cannot find a way to complete this.. My last attempt was this..
struct MyView: View {
var body: some View {
VStack {
//Other Views
AsyncImage(url: URL(string: "https://cdn2.thecatapi.com/images/7CGV6WVXq.jpg")!) { phase in
if let image = phase.image {
image
//Some image modifiers
self.imageLoaded = true // Of course this won't work in this closure but I cannot think of where else to put it.
}
}
}.redacted(reason: imageLoaded ? .placeholder : []) // <-- How to redact programmatically?
}
}
The closest solution is to use onDisappear modifier which is triggered when the view disappears (which mean the image had loaded
VStack {
//Other Views
AsyncImage(url: URL(string: "https://cdn2.thecatapi.com/images/7CGV6WVXq.jpg")!) { phase in
if let image = phase.image {
image
} else {
// When the image is loading, or has failed to load
// You can use any view (example: a loading view or placeholder)
RoundedRectangle(cornerRadius: 10)
.onDisappear {
imageLoaded = true
}
}
}
.frame(width: 300, height: 300)
}
.redacted(reason: imageLoaded ? [] : .placeholder)

WidgetKit Image from URL

I'm trying to load an image from URL to a Widget. I've done so before just fine in another project I've recently made. Now I'm trying to do the same exact method and seeing some strange errors Argument type 'URL' does not conform to expected type 'Decoder' and Cannot convert value of type 'ExampleWidgetExtension.Data' to expected argument type 'Foundation.Data'. My other project works just fine and I'm using Codable data just like before. Everything but the image seems to be working.
struct ExampleWidgetEntryView : View {
var entry: Provider.Entry
var body: some View {
VStack (alignment: .leading, spacing: 5) {
ForEach(entry.model?.example.results?.prefix(1) ?? [], id: \.id) { example in
/*
Text(example.data.title ?? "")
.fontWeight(.bold)
.font(Font.system(.caption))
.lineLimit(1)
*/
if let url = URL(string:example.data.url ?? ""),
let imageData = try? Data(from: url),
let uiImage = UIImage(data: imageData) {
Image(uiImage: uiImage)
.resizable()
.frame(width: 30, height: 30)
}
}
}
.padding(.all, 10)
}
}
There is no initializer on Data (the one in Foundation) that takes a URL in the from: field. The reason that you're getting an error is that Data(from:) expects a Decoder in that argument. You say that you have another project where you do that, but you must have some sort of custom extension on Data for that to work.
What you want is let imageData = try? Data(contentsOf: url)
As pawello points out in the comments, in looks like you may have a custom type in your project named `Data` already, so it may be necessary to do the following:
try? Foundation.Data(contentsOf: url)
You may want to consider renaming your Data type to something else to avoid namespace collisions with Foundation's Data

SwiftUI - Trying to re run a class from a different view to force data to reload

I'm new to SwiftUI and struggle sometimes with the whole 'declarative' concept but normally get there in the end...Anyway I've made a simple app that loads data from a remote JSON file on line. That part all works fine.
What I then want to do is, on a different view, I call the class FetchData - trying to get it to revisit the data source and update the variables. It appears It runs the class ok, as the print statements reappear when called - but the variables do not update with new values.
Heres code for the class to load the data.
import SwiftUI
struct GetData: Hashable, Codable, Identifiable {
var id: Int
var CODE: String
var RATE: Double
var TIME: String
var FLAG: String
var COUNTRY: String
var NAME: String
}
public class FetchData: ObservableObject {
// 1.
#Published var rates = [Rates]()
#State var showingAlert = true
init() {
let url = URL(string: "https://www.exampledata.co.uk/example.php")!
// 2.
URLSession.shared.dataTask(with: url) { [self](data, response, error) in
do {
if let processData = data {
// 3.
let decodedData = try JSONDecoder().decode([Rates].self, from: processData)
DispatchQueue.main.async {
self.rates = decodedData
print("Data Loaded from Internet")
self._showingAlert = State(initialValue: false)
}
} else {
print("No data")
self.rates=exchange
// use diferent method to load data from hardcopy if no internet avail
print("Data Loaded from Hardcopy")
self._showingAlert = State(initialValue: true)
}
} catch {
print("Error")
self.rates=exchange
print("Data Loaded from Hardcopy")
// use diferent method to load data from hardcopy if server down or general error
self._showingAlert = State(initialValue: false)
}
}.resume()
}
}
Then in the other view where I want to call it from I have ...
Text("Data last updated:" + RowData.TIME)
.font(.footnote)
.foregroundColor(Color.gray)
Button(action: {
FetchData.init()
}) {
HStack{
Image(systemName: "arrow.clockwise").resizable()
.frame(width: 15.0, height: 15.0)
Text("Reload rates")
.font(.footnote)
}}
}
2 things
First, #State should only be used in a View not an ObservableObject per the documentation. Switch #State to #Published.
Second, and what is probably your issue is in your view. How are you initializing the ObservableObject(Code not available)? it should be an #ObservedObject, #EnvironmentObject, or #StateObject. Also, make sure you aren't creating two different instances a singleton pattern or an #EnvironemntObject can help with that.

Generic parameter 'Label' could not be inferred SWIFTUI

Can someone tell me why on line 9 I get the error Generic parameter 'Label' could not be inferred
struct PlayerControlsView : View {
#State var playerPaused = true
#State var seekPos = 0.0
let player: AVPlayer
var body: some View {
HStack {
Button(action: {
self.playerPaused.toggle()
if self.playerPaused {
self.player.pause()
}
else {
self.player.play()
}
}) {
Image(systemName: playerPaused ? "play" : "pause")
.padding(.leading, CGFloat(20))
.padding(.trailing, CGFloat(20))
}
Slider(value: $seekPos, from: 0, through: 1, onEditingChanged: { _ in
guard let item = self.player.currentItem else {
return
}
let targetTime = self.seekPos * item.duration.seconds
self.player.seek(to: CMTime(seconds: targetTime, preferredTimescale: 600))
})
.padding(.trailing, CGFloat(20))
}
}
}
And of course how to fix it.
Your Slider init is incorrect. There is no version that has from: and through:. in: [0...1] is the default, though, so you don't need it anyway.
Slider(value: $seekPos, onEditingChanged: { _ in
SwiftUI error messages are generally useless. The way you find the error is to keep commenting out things until it compiles, and then add them back in.
However, what you're doing here isn't going to work. You can't put a AVPlayer inside a View. A View is a struct, and they get created, copied, and destroyed all the time. See How to stream remote audio in iOS 13? (SwiftUI).