Swift issues getting images from URL in forEach list - ios

I'm currently trying to transition from a web developer only to a mobile and web developer and have been having great difficulties learning swift, I think I learn something and write it only to have numerous errors. Right now I have the following code to get an image from my server inside of a foreach loop.
struct MainNotLoggedInView: View {
#ObservedObject var fetcher = Fetcher()
func getPic(urlLink: String) -> Image? {
let baseURL = "https://mywebsite.com"
let url = URL(string: baseURL + urlLink)
let data = try? Data(contentsOf: url!)
let image = UIImage(data: data!)
let image2 = Image(uiImage: (image ?? nil)!)
return image2
}
var body: some View{
VStack{
TabView {
ScrollView {
ZStack {
if fetcher.hasFinished == true {
VStack {
Text("Featured").font(.largeTitle).padding()
ForEach(fetcher.ac?.ac.featuredAc ?? []) { result in
VStack {
Text(result.name)
Image(self.getPic(urlLink: result.acPic))
}
}
}
} else {
Text("Featured").font(.largeTitle).padding()
}
}
}.tabItem {
Text("Featured").font(.subheadline)
}
OtherView().tabItem {
Text("Other").font(.subheadline)
}
}
}
}
}
I assume in order to figure out what is going on, I should also include the JSON fetcher script and the structures, which are below
import SwiftUI
import Combine
import Foundation
public struct AcModel: Codable, Identifiable {
public let id: Int
public let name: String
public let acPic: String
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case acPic = "picture_link"
}
}
public struct AcModel2: Codable {
public let location: String
private var popular: [String:AcModel]
public var popularAc: [AcModel] {
get {
return Array(self.popular.values)
}
}
private var featured: [String:AcModel]
public var featuredAc: [AcModel] {
get {
return Array(self.featured.values)
}
}
}
public struct AcModel: Codable {
public let ac: AcModel2
}
public class Fetcher: ObservableObject {
public let objectWillChange = PassthroughSubject<Fetcher,Never>()
#Published var hasFinished: Bool = false {
didSet {
objectWillChange.send(self)
}
}
var ac: AcModel?
init(){
guard let url = URL(string: "https://mywebsite.com/api/loadactivitiesguest") else { return }
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode(AcModel.self, from: d)
DispatchQueue.main.async {
self.ac = decodedLists
self.hasFinished = true
print("dispatching")
}
} else {
print("No Data")
}
} catch {
print("Error")
}
}.resume()
}
}
I'm sure because I'm a web developer by trade I'm thinking about it the wrong way, but as you can tell from the code, I'm trying to get the picture inside the foreach using the swift method of retrieving pictures from a server, and trying to display it. The code as I have it written has the error "Type of expression is ambiguous without more context". This error is on the line inside the actual view where I try to call the function; "Image(self.getPic(urlLink: result.acPic))"
I bolded the actual words highlight by the error.
Of course, the acPic variable is an actual variable, which is not optional, of the structure. I've seen a lot of other StackOverflow posts about this error, but as with several of the swift errors, it seems the same error message can be caused by several difference types of code with different purposes, and in combination with my lack of swift experience, I have difficulty understanding the relation between what they did wrong and what I did wrong.
From reading around however, I read that I should use something called Kingfisher to cache the images as there is hundreds. How do I install those git repositories into my project? Just a side question, no need to really answer there's probably hundreds of videos.
Any help would be appreciated.

Update
Image(self.getPic(urlLink: result.acPic))
to
self.getPic(urlLink: result.acPic)
Image hasn't a init method which gets an optional Image object and returns an Image object

Related

SwiftUI Navigation - List loading multiple time after navigating from details

I am creating a SwiftUI List with Details.
This list is fetching JSON data from Firebase Realtime. The data consist of 5 birds with an ID, a name and an image URL.
My problem is the following:
Each time I click on the back button after I navigate to details, the data get doubled every single time, what am I doing wrong? (see screenshots).
I am using MVVM design pattern, I am listening and removing that listener every time the View appears and disappears.
Please, find the code below:
Main View:
var body: some View {
NavigationStack {
List(viewModel.birds) { bird in
NavigationLink(destination: DetailsView(bird: bird)) {
HStack {
VStack(alignment: .leading) {
Text(bird.name).font(.title3).bold()
}
Spacer()
AsyncImage(url: URL(string: bird.imageURL)) { phase in
switch phase {
// downloading image here
}
}
}
}
}.onAppear {
viewModel.listentoRealtimeDatabase()
}
.onDisappear {
viewModel.stopListening()
}.navigationTitle("Birds")
}
}
DetailsView:
struct DetailsView: View {
var bird: Bird
var body: some View {
Text("\(bird.name)")
}
}
Model:
struct Bird: Identifiable, Codable {
var id: String
var name: String
var imageURL: String
}
View Model:
final class BirdViewModel: ObservableObject {
#Published var birds: [Bird] = []
private lazy var databasePath: DatabaseReference? = {
let ref = Database.database().reference().child("birds")
return ref
}()
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
func listentoRealtimeDatabase() {
guard let databasePath = databasePath else {
return
}
databasePath
.observe(.childAdded) { [weak self] snapshot in
guard
let self = self,
var json = snapshot.value as? [String: Any]
else {
return
}
json["id"] = snapshot.key
do {
let birdData = try JSONSerialization.data(withJSONObject: json)
let bird = try self.decoder.decode(Bird.self, from: birdData)
self.birds.append(bird)
} catch {
print("an error occurred", error)
}
}
}
func stopListening() {
databasePath?.removeAllObservers()
}
}
screenshot how it should be

How to connect enum/switch with an API value for a specific case swiftUI

I have a currency converter calculator application, Where data is fetched through the API. I am trying to convert inputted data through the enum and switch but unfortunately, I am not sure what is the mistake. Please Can someone help me to understand what shall I do?
You may find my GitHub Link for a project below
https://github.com/Chokaaaa/CurMe
This is my Fetch file
import SwiftUI
class FetchData: ObservableObject {
#Published var coversionData: [Currency] = []
#Published var baseCode = "USD"
init() {
fetch()
}
func fetch() {
let url = "https://open.exchangerate-api.com/v6/latest?base=\(baseCode)"
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: url)!) { data, _, _ in
guard let JSONData = data else {return}
do {
let conversion = try JSONDecoder().decode(Conversion.self, from: JSONData)
DispatchQueue.main.async {
self.coversionData = conversion.rates.compactMap({ (key,value) -> Currency? in
return Currency(currencyName: key, currencyValue: value)
})
.filter({ Currency in
Currency.currencyName == self.filteredCurrency
})
}
}
catch {
print(error)
}
}
.resume()
}
func updateData(baseCode: String) {
self.baseCode = baseCode
self.coversionData.removeAll()
fetch()
}
}
Below you may find an enum where I use a switch with a return of input from the custom number pad (like a calculator) and multiplied on the dummy value. I think I need to fix something in the switch case. Please guys someone help me!! I am struggling.
Bellow, you may find a code for a enum/switch
import SwiftUI
struct CurrencyView: View {
#StateObject var viewModel = FetchData()
enum currencyChoice {
case Kazakhstan, Rubles, Usa
func image() -> Image {
switch self {
case .Kazakhstan: return Image("kz")
case .Rubles: return Image("rub")
case .Usa: return Image("usa")
}
}
func operation(_ input: Double) -> Double {
switch self {
case .Kazakhstan: return (input * 2)
case .Rubles: return (input * 3)
case .Usa: return (input * 5)
}
}
}
var function : currencyChoice
#Binding var state : CalculationState
var body: some View {
return function.image()
.resizable()
.scaledToFill()
.frame(width: 80, height: 80)
.cornerRadius(40)
.onTapGesture {
state.currentNumber = function.operation(state.currentNumber)
}
}
}
In your case, enum is not suggested with store property so better to avoid it or use the static property of enum.
Definitely it'll fix your issue.

Refresh value in text label from JSON, with Xcode

I am learning Swift to develop macOS applications and I ran into a problem. I am trying to get certain data from a JSON from the internet. I have managed to get such data and put it in simple text labels in the view but when I run Xcode and get the values, if the values from the JSON get updated, I can't see it reflected in my app. I know that I must perform a function to refresh the data but what I have always found is the function to refresh the data that is in a table, not a simple text label.
Regardless of this problem, if I wanted to add a table with 3 columns (each structure has 3 data, at least) with the values from the JSON. When I run the refresh of the table, I should include in the function the function that gets the data from the internet, right? I'm a bit lost with this too.
This is what I have:
ViewController.swift
import Cocoa
class ViewController: NSViewController, NSTextFieldDelegate {
let user_items = UserItems()
#IBOutlet var item_salida: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
let struc_item = user_items.Item_Struct()
let position = struc_item.firstIndex(where: { $0.name == "Leanne Graham" })!
print(struc_item[position].state!)
item_salida.stringValue = struc_item[position].state!
} }
Struct.swift
import Foundation
import SwiftyJSON
// MARK: - Dato
struct User: Codable {
var name: String?
var username: String?
var email: String?
}
typealias Datos = [User]
class UserItems {
func Item_Struct() -> Datos {
let urlString = "https://jsonplaceholder.typicode.com/users"
var items_available: [User] = []
if let url = NSURL(string: urlString){
if let data = try? NSData(contentsOf: url as URL, options: []){
let items = try! JSONDecoder().decode([User].self, from: data as Data)
for item in items {
items_available.append(item)
}
}
}
return items_available
}
}
Thanks, a lot!
First of all - as you are learning Swift - please stop using snake_case variable names and also the NS.. classes NSURL and NSData.
Never load data from a remote URL with synchronous Data(contentsOf. It blocks the thread.
You need URLSession and an asynchronous completion handler.
// MARK: - Dato
struct User: Codable {
let name: String
let username: String
let email: String
}
typealias Datos = [User]
class UserItems {
func loadData(completion: #escaping (Datos) -> Void) {
let url = URL(string: "https://jsonplaceholder.typicode.com/users")!
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error { print(error); return }
do {
let items = try JSONDecoder().decode([User].self, from: data!)
completion(items)
} catch {
print(error)
}
}.resume()
}
}
And use it in the controller
class ViewController: NSViewController, NSTextFieldDelegate {
#IBOutlet var itemSalida: NSTextField!
let userItems = UserItems()
override func viewDidLoad() {
super.viewDidLoad()
userItems.loadData { users in
if let position = users.firstIndex(where: { $0.name == "Leanne Graham" }) {
DispatchQueue.main.async {
print(users[position].username)
self.itemSalida.stringValue = users[position].username
}
}
}
}
}
And forget SwiftyJSON. It's not needed anymore in favor of Codable.

How to add a function that automatically moves to the second API key in Rxswift

One API key can only make 100 requests per day. So one API key can't handle a lot of requests per day. There are other ways to solve this problem, but I would like to solve this problem by entering various API keys. For example, if the first API key makes 100 requests and the request value returns as an error, I want to add a function that automatically moves to the second API key.
Can you tell me how to make it with Rxswift?
I would appreciate any help you can provide.
The code is as below.
private func loadTopNews() {
let resource = Resource<ArticleResponse>(url: URL(string: "https://newsapi.org/v2/top-headlines?country=\(selectedLanguagesCode[0])&sortBy=%20popularity&apiKey=\(apiKey[0])")!)
URLRequest.load(resource: resource)
.subscribe(onNext: { articleResponse in
let topArticle = articleResponse.articles.first
self.articleVM = ArticleViewModel(topArticle!)
}).disposed(by: disposeBag)
}
struct Resource<T: Decodable> {
let url: URL
}
extension URLRequest {
static func load<T>(resource: Resource<T>) -> Observable<T> {
return Observable.just(resource.url)
.flatMap { url -> Observable<Data> in
let request = URLRequest(url: url)
return URLSession.shared.rx.data(request: request)
}.map { data -> T in
return try JSONDecoder().decode(T.self, from: data)
}
}
}
struct ArticleResponse: Decodable {
let articles: [Article]
}
struct Article: Decodable {
let title: String
let publishedAt: String
let urlToImage: String?
let url: String
}
struct ArticleListViewModel {
let articlesVM: [ArticleViewModel]
}
extension ArticleListViewModel {
init(_ articles: [Article]) {
self.articlesVM = articles.compactMap(ArticleViewModel.init)
}
}
extension ArticleListViewModel {
func articleAt(_ index: Int) -> ArticleViewModel {
return self.articlesVM[index]
}
}
struct ArticleViewModel {
let article: Article
init(_ article: Article) {
self.article = article
}
}
extension ArticleViewModel {
var title: Observable<String> {
return Observable<String>.just(article.title)
}
var publishedAt: Observable<String> {
return Observable<String>.just(article.publishedAt)
}
var urlToImage: Observable<String> {
return Observable<String>.just(article.urlToImage ?? "NoImage")
}
var url: Observable<String> {
return Observable<String>.just(article.url)
}
}
I wrote an article covering this very thing (albeit in a different context): RxSwift and Handling Invalid Tokens
The above article will help if you are making multiple requests at the same time and need to restart all of them with the new token. It might be overkill in this specific case.
To solve this problem, you need:
A function that will build a resource with a given api key
An Observable that emits a different API key whenever it's subscribed to.
Once you have those two pieces, you can just retry your subscription until one of the keys works.
Solution
I suggest you use the above as clues and try to solve the problem yourself. Then you can check your answer against the solution below...
For item 1, I see that you need two arguments to create a resource. So I suggest making a function factory that will produce a function that takes an apiKey. Like this:
func makeResource(selectedLanguagesCode: String) -> (String) -> Resource<ArticleResponse> {
{ apiKey in
Resource<ArticleResponse>(url: URL(string: "https://newsapi.org/v2/top-headlines?country=\(selectedLanguagesCode)&sortBy=%20popularity&apiKey=\(apiKey)")!)
}
}
Note that this function is not part of the class. It doesn't need self.
For item 2, we need a function that takes the array of apiKeys and produces an Observable that will emit a different key each time it's subscribed to:
Something like this should work:
func produceApiKey(apiKeys: [String]) -> Observable<String> {
var index = 0
return Observable.create { observer in
observer.onNext(apiKeys[index % apiKeys.count])
observer.onCompleted()
index += 1
return Disposables.create()
}
}
Again, this function doesn't need self so it's not part of the class.
Now that you have these two elements, you can use them in your loadTopNews() method. Like this:
private func loadTopNews() {
produceApiKey(apiKeys: apiKey)
.map(makeResource(selectedLanguagesCode: selectedLanguagesCode[0]))
.flatMap(URLRequest.load(resource:))
.retry(apiKey.count - 1)
.subscribe(onNext: { articleResponse in
let topArticle = articleResponse.articles.first
self.articleVM = ArticleViewModel(topArticle!)
})
.disposed(by: disposeBag)
}

Swift Does not see data

I am trying to parse the data and display on the screen but i am getting " Value of type 'EmployeeData' has no member 'employee_name' "
What i am missing ?
I created my struct, parsed data and tried to divide into two parts. first part will be related with listing, second part is all data.
struct EmployeeData: Codable {
var data: Employee
var status: String
}
struct Employee: Codable {
var employee_name: String
var employee_salary: String
var employee_age: String
}
class WebServices {
func getData(completion: #escaping (EmployeeData?) -> ()){
guard let url = URL(string:"http://dummy.restapiexample.com/api/v1/employees")
else { fatalError("There is error!") }
URLSession.shared.dataTask(with: url) { (data, response,error) in
guard let data = data, error == nil else {
DispatchQueue.main.async{
completion(nil)
}
return
}
let empleyees = try? JSONDecoder().decode(EmployeeData.self, from: data)
DispatchQueue.main.async {
completion(empleyees)
}
}.resume()
}
}
class MVDesingnListView: ObservableObject {
}
struct MVDesignCellView {
let employeeDatas: EmployeeData
init(employeeDatas: EmployeeData) {
self.employeeDatas = employeeDatas
}
var employee_name: String {
self.employeeDatas.employee_name
}
}
The compiler is all right. Your struct EmployeeData has no member employee_name.
You need to go to the employee first, to get her name:
var employee_name: String {
self.employeeDatas.data.employee_name
}
should do the job.

Resources