WidgetKit Image from URL - swiftui

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

Related

Using TimelineView to use in a custom view modifier

I am looking to use the Timelineview to automatically update the background colour for a popup sheet that is displaying a list.
I am trying to figure out how to only call it once so I can use it for multiple sections in one list and in different views that display the type of data but with different information.
I have custom colours that I would like to use and call them from the assets catalogue.
A Sample Code:
List {
Section(header: Text("Ear")
.modifier(SectionHeaderModifer())) {
ForEach(Ear.indices, id: \.self) {index in
VStack {
VStack() {
Spacer()
Text(Ear[index].name)
.modifier(NameModifier())
}
HStack {
Text(String(format: "$%.2f", Ear[index].value))
.modifier(ValueModifier())
Text(Ear[index].code)
.modifier(CodeModifier())
}
.frame(width:270)
}
.modifier(RowFormatModifier())
.listRowBackground(Color("\(hour)").opacity(0.9))
.listRowSeparator(.hidden)
}
}
Section(header: Text("Nose")
.font(.title)
.fontWeight(.thin)) {
ForEach(Nose.indices, id: \.self) {index in
VStack {
VStack() {
Spacer()
Text(Nose[index].name)
.modifier(NameModifier())
}
HStack {
Text(String(format: "$%.2f", Nose[index].value))
.modifier(ValueModifier())
Text(Nose[index].code)
.modifier(CodeModifier())
}
.frame(width:270)
}
.frame(width: 330, height:95, alignment: .center)
.modifier(RowFormatModifier())
.listRowBackground(Color("18").opacity(0.9))
.listRowSeparator(.hidden)
}
}
The second section is using the Color("18") which is in reference to 1800 just for reference as well. I have custom colour for each hour and thus why I am using the Timeline view to update the variable
The hour variable would be updated by the Timelineview as:
TimelineView(.animation) {context in
let now = Date()
let hour = Calendar.current.component(.hour, from: now)
}
I am not sure and I have tried and failed to try to put this in a ViewModifier where I could update the list row colour.
struct ChangeRowColor: ViewModifier {
func body(content: Content) -> some View {
content
TimelineView(.animation) {context in
let now = Date()
let hour = Calendar.current.component(.hour, from: now)
}
.listRowBackground(Color("\(hour)").opacity(0.9))
}
I know this is wrong because it can't see the hour variable.
Any suggestions? Where should I call the timeline to determine the hour? I only want to do it once and pass this into the different sections and other views that are of similar format.
Thanks!

swiftui 2.0 Image Gallery onTapgesture only shows the first image in the array

I am creating a reusable gallery view for an app and am having difficulties when any picture is tapped it suppose to become full screen but only the first picture in the array is shown every time no matter the picture tapped. Below is my code, thanks.
import SwiftUI
struct ReusableGalleryView: View {
let greenappData: GreenAppNews
let gridLayout: [GridItem] = Array(repeating: GridItem(.flexible()), count: 3)
#State private var fullscreen = false
#State private var isPresented = false
var body: some View {
VStack{
ScrollView{
LazyVGrid(columns: gridLayout, spacing: 3) {
ForEach(greenappData.GreenAppGallery, id: \.self) { item in
Image(item)
.resizable()
.frame(width: UIScreen.main.bounds.width/3, height: 150)
.onTapGesture {
self.isPresented.toggle()
print(" tapping number")
}
.fullScreenCover(isPresented: $isPresented) {
FullScreenModalView( imageFiller: item)
}
.background(Color.gray.opacity(0.5))
}
}
.padding(.horizontal)
}
}
}
}
This is an example of the json data:
{
"id" : "1",
"GreenAppGallery" : [
"Picture-1",
"Picture-2",
"Picture-3",
"Picture-4",
"Picture-5",
"Picture-6",
"Picture-7",
"Picture-8",
"Picture-9",
"Picture-10"
]
},
fullScreenCover, like sheet tends to create this type of behavior in iOS 14 when using isPresented:.
To fix it, you can change to the fullScreenCover(item: ) form.
Not having all of your code, I'm not able to give you an exact version of what it'll look like, but the gist is this:
Remove your isPresented variable
Replace it with a presentedItem variable that will be an optional. Probably a datatype that is in your gallery. Note that it has to conform to Identifiable (meaning it has to have an id property).
Instead of toggling isPresented, set presentedItem to item
Use fullScreenCover(item: ) { presentedItem in FullScreenModalView( imageFiller: presentedItem) } and pass it your presentedItem variable
Move the fullScreenCover so that it's attached to the ForEach loop rather than the Image
Using this system, you should see it respond to the correct item.
Here's another one of my answers that covers this with sheet: #State var not updated as expected in LazyVGrid

UIVisualEffectView does not work in iOS 14 Widget

I put on an image and it fills widget view.
And I made small view and wanted this view shows blurred imaged of behind image.
(image and small view are in the ZStack)
I used few code (something like Option 2 from Is there a method to blur a background in SwiftUI?)
but the result is like
I think that yellow box means 'VisualEffectView doesn't work in WidgetKit.'
So I wonder if there is other technique to show small view that shows blurred behind image?
I think that yellow box means 'VisualEffectView doesn't work in
WidgetKit.'
Yes, specifically because you can't use UIViewRepresentable in Widgets. See:
Display UIViewRepresentable in WidgetKit
It means that the only option is to use SwiftUI code. A possible solution is here (Option 1):
Is there a method to blur a background in SwiftUI?
struct WidgetEntryView: View {
var entry: Provider.Entry
var body: some View {
ZStack {
Image("testImage")
.blur(radius: 10)
}
.edgesIgnoringSafeArea(.all)
}
}
I found some workaround
first, i put the original picture
then put a picture which applied Gaussian filter on the original picture
and add clipShape to the filtered image.
object for argument of clipShape needs to confirm Shape protocol like
struct MyShape: Shape {
func path(in rect: CGRect) -> Path {
RoundedRectangle(cornerRadius: 10.0).path(...)
}
}
and I found Gaussian filter code from
https://gist.github.com/Zedd0202/8d3e567161d0c92e7d585bb74e926413#file-applyblur_usingclamp-swift
pseudo code
ZStack {
Image("image")
Image("image")
.clipShape(YourShape())
.frame(...)
.padding(...)
}
---
extension UIImage {
func applyBlur_usingClamp(radius: CGFloat) -> UIImage {
let context = CIContext()
guard let ciImage = CIImage(image: self),
let clampFilter = CIFilter(name: "CIAffineClamp"),
let blurFilter = CIFilter(name: "CIGaussianBlur") else {
return self
}
clampFilter.setValue(ciImage, forKey: kCIInputImageKey)
blurFilter.setValue(clampFilter.outputImage, forKey: kCIInputImageKey)
blurFilter.setValue(radius, forKey: kCIInputRadiusKey)
guard let output = blurFilter.outputImage,
let cgimg = context.createCGImage(output, from: ciImage.extent) else {
return self
}
return UIImage(cgImage: cgimg)
}
}
---
struct YourShape: Shape {
func path(in rect: CGRect) -> Path {
RoundedRectangle(cornerRadius: 10.0)
.path(in: CGRect(...))
}
}
then you will get something like this
Update
looks like widget has kind of memory limit
if you run this code on real device it can be crashed (most time simulator works find, in my case)
you can resolve this problem through adjusting radius value of applyBlur_usingClamp method. (250 crashed, 100 is fine for me)

loading image in swiftUI causing nil while unwrapping

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)!,

SwiftUI - animating a new image inside the current view

I have a View where I use a Picture(image) subview to display an image, which can come in different height and width formats.
The reference to the image is extracted from an array, which allows me to display different images in my View, by varying the reference. SwiftUI rearrange the content of view for each new image
I would like an animation on this image, say a scale effect, when the image is displayed
1) I need a first .animation(nil) to avoid animating the former image (otherwise I have an ugly fade out and aspect ratio deformation). Seems the good fix
2) But then I have a problem with the scaleEffect modifier (even if I put it to scale = 1, where it should do nothing)
The animation moves from image 1 to image 2 by imposing that the top left corner of image 2 starts from the position of top left corner of image 1, which, with different widths and heights, provokes a unwanted translation of the image center
This is reproduced in the code below where for demo purposes I'm using system images (which are not prone to bug 1))
How can I avoid that ?
3) In the demo code below, I trigger the new image with a button, which allows me to use an action and handle "scale" modification and achieve explicitly the desired effect. However in my real code, the image modification is triggered by another change in another view.
Swift knows that, hence I can use an implicit .animation modifier.
However, I can't figure out how to impose a reset of "scale" for any new image and perform my desired effect.
If I use onAppear(my code), it only works for the first image displayed, and not the following ones.
In the real code, I have a Picture(image) view, and Picture(image.animation()) does not compile.
Any idea how to achieve the action in the below code in the Button on an implicit animation ?
Thanks
import SwiftUI
let portrait = Image(systemName: "square.fill")
let landscape = Image(systemName: "square.fill")
struct ContentView: View {
#State var modified = false
#State var scale: CGFloat = 1
var body: some View {
return VStack(alignment: .center) {
Pictureclip(bool: $modified)
.animation(nil)
.scaleEffect(scale)
.animation(.easeInOut(duration: 1))
Button(action: {
self.modified.toggle()
self.scale = 1.1
DispatchQueue.main.asyncAfter(deadline: .now() + 1)
{self.scale = 1}
}) {
Text("Tap here")
.animation(.linear)
}
}
}
}
struct Pictureclip: View {
#Binding var bool: Bool
var body: some View {
if bool == true {
return portrait
.resizable()
.frame(width: 100, height: 150)
.foregroundColor(.green)
} else {
return landscape
.resizable()
.frame(width: 150, height: 100)
.foregroundColor(.red)
}
}
}
I have a semi answer to my question, namely points 1 & 2 (here with reference to two jpeg images in the asset catalog)
import SwiftUI
let portrait = Image("head")
let landscape = Image("sea")
struct ContentView: View {
#State var modified = false
#State var scale: CGFloat = 0.95
var body: some View {
VStack(){
GeometryReader { geo in
VStack {
Picture(bool: self.modified)
.frame(width: geo.size.width * self.scale)
}
}
Spacer()
Button(action: {
self.scale = 0.95
self.modified.toggle()
withAnimation(.easeInOut(duration: 0.5)){
self.scale = 1
}
}) {
Text("Tap here")
}
}
}
}
struct Picture: View {
var bool: Bool
var body: some View {
if bool == true {
return portrait
.resizable().aspectRatio(contentMode: .fit)
.padding(.all,6.0)
} else {
return landscape
.resizable().aspectRatio(contentMode: .fit)
.padding(.all,6.0)
}
}
}
This solution enables scaling without distorting the aspect ratio of the new image during the animation. But It does not work in a code where the image update is triggered in another view. I guess I have to restructure my code, either to solve my problem or to expose it more clearly.
Edit: a quick and dirty solution is to put the triggering code (here the action code in the button) in the other view. Namely, put in view B the code that animates view A, with a state variable passed to it (here, "scale"). I'm sure there are cleaner ways, but at least this works.
I am not sure about it, but maybe it can be helpful for you.
Use DataBinding structure. I use it like this:
let binding = Binding<String>(get: {
self.storage
}, set: { newValue in
self.textOfPrimeNumber = ""
self.storage = newValue
let _ = primeFactorization(n: Int(self.storage)!, k: 2, changeable: &self.textOfPrimeNumber)
})