I don't know why but when I open the app, it takes a while to load all the data on the screen, until there remains a white screen without content. All the data loaded is downloaded from an API. What should I do to make it better?
App Loaded after about 10 seconds:
I'll post below how I'm parsing all the data.
ViewController.swift:
import UIKit
class ViewController: UIViewController, UICollectionViewDataSource, UITableViewDataSource {
#IBOutlet weak var tableViewTopSell: UITableView!
#IBOutlet var collectionView: UICollectionView!
#IBOutlet weak var collectionViewBanner: UICollectionView!
var dataSource: [Content] = [Content]()
var dataBanner: [Banner] = [Banner]()
var dataTopSold: [Top10] = [Top10]()
override func viewDidLoad() {
super.viewDidLoad()
//SetupNavBarCustom
self.navigationController?.navigationBar.CustomNavigationBar()
let logo = UIImage(named: "tag.png")
let imageView = UIImageView(image:logo)
self.navigationItem.titleView = imageView
//CallAPIData
getTopSold { (data) in
DispatchQueue.main.async {
self.dataTopSold = data
self.tableViewTopSell.reloadData()
}
}
getBanner { (data) in
DispatchQueue.main.async {
self.dataBanner = data
self.collectionViewBanner.reloadData()
}
}
getAudiobooksAPI { (data) in
DispatchQueue.main.async {
self.dataSource = data
self.collectionView.reloadData()
}
}
}
//CollectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if (collectionView == self.collectionView) {
return self.dataSource.count
}else{
return self.dataBanner.count
}}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if (collectionView == self.collectionView) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! CollectionViewCell
let content = self.dataSource[indexPath.item]
cell.bookLabel.text = content.descricao
cell.bookImage.setImage(url: content.urlImagem, placeholder: "")
return cell
}else if (collectionView == self.collectionViewBanner) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCellBanner", for: indexPath) as! CollectionViewCell
let content = self.dataBanner[indexPath.item]
cell.bannerImage.setImage(url: content.urlImagem, placeholder: "")
return cell
}
return UICollectionViewCell()
}
//TableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataTopSold.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "topSoldCell", for: indexPath) as! TableViewCell
let content = self.dataTopSold[indexPath.item]
cell.labelNomeTopSell.text = content.nome
cell.imageViewTopSell.setImage(url: content.urlImagem, placeholder: "")
return cell
}
}
extension UIImageView{
func setImage(url : String, placeholder: String, callback : (() -> Void)? = nil){
self.image = UIImage(named: "no-photo")
URLSession.shared.dataTask(with: NSURL(string: url)! as URL, completionHandler: { (data, response, error) -> Void in
guard error == nil else{
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
self.image = image
if let callback = callback{
callback()
}
})
}).resume()
}
}
DataStore.swift:
import Foundation
import UIKit
func getBanner(_ completion:#escaping ([Banner])->Void) {
let url = URL(string: "https://alodjinha.herokuapp.com/banner")
let session = URLSession.shared
guard let unwrappedURL = url else { print("Error unwrapping URL"); return }
let dataTask = session.dataTask(with: unwrappedURL) { (data, response, error) in
guard let unwrappedDAta = data else { print("Error unwrapping data"); return }
do {
let jsonBanner = try JSONDecoder().decode(BannerData.self, from: unwrappedDAta)
completion(jsonBanner.data)
} catch {
print("Could not get API data. \(error), \(error.localizedDescription)")
}
}
dataTask.resume()
}
func getTopSold(_completion:#escaping ([Top10])->Void) {
let url = URL(string: "https://alodjinha.herokuapp.com/produto/maisvendidos")
let session = URLSession.shared
guard let unwrappedURL = url else { print("Error url"); return}
let dataTask = session.dataTask(with: unwrappedURL) { (data, response, error) in
guard let unwrappedData = data else { print("Error data"); return}
do {
let jsonTop10 = try JSONDecoder().decode(Top10Data.self, from: unwrappedData)
_completion(jsonTop10.data)
}catch{
print("Could no get API data")
}
}
dataTask.resume()
}
Model.swift:
import Foundation
//Categorias
struct Contents : Decodable {
let data : [Content]
}
struct Content : Decodable {
let id : Int
let descricao : String
let urlImagem : String
}
//Banner
struct BannerData : Decodable {
let data : [Banner]
}
struct Banner : Decodable {
let id : Int
let urlImagem : String
let linkUrl : String
}
//Top10
struct Top10Data:Decodable {
let data: [Top10]
}
struct Top10:Decodable {
let id : Int
let nome : String
let urlImagem : String
}
Apart from it's a heavy network loading VC , you Currently don't have any problems loading the data as all are out of main thread , the only problem is loading the images as when you scroll it re-download the image again which may be just downloaded for this i recommend using SDWebImage which will take care of the download & cache for you , your main problem may be low network speed for many requests
Another thing to manage this network problem you may serial queue the download of the data that will help you load one part fastly and display it which will make the impression to the user that the app is in request for more data instead of make all the requests at once
Related
I have backend api it contains all values i can see those values in postman.. but while parsing i am unable to download all values from api.. some times i am getting all values.. some times i am not getting only some values.. if i close app and run again then i am getting all values.. again if i close and run or if i go to other viewcontroller and coming back to home then i am missing some values. if i print jsonObj i am not getting all values from api.. why is this happening?
here is my code:
import UIKit
import SDWebImage
struct JsonData {
var iconHome: String?
var typeName: String?
var id: String?
init(icon: String, tpe: String, id: String) {
self.iconHome = icon
self.typeName = tpe
self.id = id
}
}
class HomeViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UITextFieldDelegate {
#IBOutlet weak var collectionView: UICollectionView!
var itemsArray = [JsonData]()
override func viewDidLoad() {
super.viewDidLoad()
homeServiceCall()
//Do any additional setup after loading the view.
collectionView.delegate = self
collectionView.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return itemsArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! HomeCollectionViewCell
let aData = itemsArray[indexPath.row]
cell.paymentLabel.text = aData.typeName
cell.paymentImage.sd_setImage(with: URL(string:aData.iconHome ?? ""), placeholderImage: UIImage(named: "varun finance5_icon"))
return cell
}
//MARK:- Service-call
func homeServiceCall(){
let urlStr = "https://dev.com/webservices//getfinancer"
let url = URL(string: urlStr)
URLSession.shared.dataTask(with: url!, completionHandler: {(data, response, error) in
guard let respData = data else {
return
}
guard error == nil else {
print("error")
return
}
do{
let jsonObj = try JSONSerialization.jsonObject(with: respData, options: .allowFragments) as! [String: Any]
print("the home json is \(jsonObj)")
let financerArray = jsonObj["financer"] as! [[String: Any]]
for financer in financerArray {
guard let id = financer["id"] as? String else { break }
guard let pic = financer["icon"] as? String else { break }
guard let typeName = financer["tpe"] as? String else { break } //changed this one to optional too. Avoid force-unwrapping. Keep everything safe
let jsonDataObj = JsonData(icon: pic, tpe: typeName, id: id)
self.itemsArray.append(jsonDataObj)
}
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
catch {
print("catch error")
}
}).resume()
}
}
Please help me in the above code.
Try network call with background thread.
DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async {
self.homeServiceCall()
}
And the URL is legal with double / as below ?
let urlStr = "https://dev.com/webservices//getfinancer"
And check the all of your backend data's is compatible with your JSONSerialization type of JsonData struct.
I hope it helps.
Basically I'm building an app that will consume data from a rest API.
In it's data also exists a couple images which came as URL's to be downloaded. So I though it would be better to download these images only once, then I cache them to reuse it on my UITableView Cells so on.
What should I do to use this feature correctly?
So let's get started. First I created the following class to handle download/cache images:
class ImageService {
static let cache = NSCache<NSString, UIImage>()
static func downloadImage(url:URL, completion: #escaping (_ image:UIImage?)->()) {
let dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
var downloadedImage:UIImage?
if let data = data {
downloadedImage = UIImage(data: data)
}
if downloadedImage != nil {
self.cache.setObject(downloadedImage!, forKey: url.absoluteString as NSString)
}
DispatchQueue.main.async {
completion(downloadedImage)
}
}
dataTask.resume()
}
static func getImage(url:URL, completion:#escaping (_ image:UIImage?)->()) {
if let image = self.cache.object(forKey: url.absoluteString as NSString) {
completion(image)
} else {
downloadImage(url: url, completion: completion)
}
}
}
Facing the code above, in my UITableView class I just called:
class TableViewController: UITableViewController {
private var articlesViewModel:ArticleViewModel?
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.showsVerticalScrollIndicator = false
self.navigationController?.navigationBar.prefersLargeTitles = true
//Call Network
Networking.getApiData(url: Networking.urlRequest) { [weak self] (articles) in
//Data from API
self?.articlesViewModel = ArticleViewModel(modelRef: articles)
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
//MARK: TableView DataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return 1 //Static
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.articlesViewModel?.updateTableCount() ?? 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.cellIdentifier, for: indexPath) as! TableViewCell
if let title = self.articlesViewModel?.updateTableTitleForIndex(indexpath: indexPath.row) {
cell.titleCell.text = title
}
if let description = self.articlesViewModel?.updateTableDescriptionForIndex(indexpath: indexPath.row) {
cell.descriptionCell.text = description
}
if let pictureURL = self.articlesViewModel?.updateTableImageForIndex(indexpath: indexPath.row) {
ImageService.getImage(url: URL(string: pictureURL)!) { (finalImage) in
cell.imageCell.image = finalImage
}
}
return cell
}
}
However, after I run the App I got the following result:
Seems that all cached images came before I get the result from API on ViewDidLoad
If I scroll down then up again, I got the following result:
Seems that everything got kinda "reordered", then everything looks ok.
Additional Classes:
ViewModel:
class ArticleViewModel {
static var articlesRef:[Article]!
init(modelRef:[Article]) {
ArticleViewModel.articlesRef = modelRef
}
//MARK: Functions
func updateTableCount() -> Int {
return ArticleViewModel.self.articlesRef.count
}
func updateTableTitleForIndex(indexpath:Int) -> String {
return ArticleViewModel.self.articlesRef[indexpath].title ?? ""
}
func updateTableDescriptionForIndex(indexpath:Int) -> String {
return ArticleViewModel.self.articlesRef[indexpath].description ?? ""
}
func updateTableImageForIndex(indexpath:Int) -> String {
return ArticleViewModel.self.articlesRef[indexpath].urlToImage ?? ""
}
}
Networking Layer:
class Networking {
static var urlRequest = "https://newsapi.org/v2/everything?q=apple&from=2019-06-05&to=2019-06-05&sortBy=popularity&apiKey=04d5f33acdde48f1a22a90f46fc483b5"
static func getApiData(url: String?, _ completion:#escaping([Article]) -> ()) {
guard let unrwpUrl = url else {return}
let request = URL(string: unrwpUrl)
URLSession.shared.dataTask(with: request!) { (data, request, error) in
if let data = data {
do {
let decodedData = try JSONDecoder().decode(Articles.self, from: data)
completion(decodedData.articles)
}catch{
print(error.localizedDescription)
}
}
}.resume()
}
}
Model:
struct Articles: Decodable {
let articles:[Article]
}
struct Article: Decodable {
let title:String?
let author:String?
let description:String?
let urlToImage:String?
}
I'm having troubles parsing images into my table view reusable cell. Hope anyone can help. Sorry if there is gonna be too many code, just don't want to miss something. Here is what my code look like:
import UIKit
struct OfferList: Decodable {
let data: [CarItems]
let status: String
let count: Int }
struct CarItems: Decodable {
let id: String
let image: URL
let manufacturer: String
let model: String
let priceNet: Double
let priceOld: Int
let priceGross: Double
let powerKw: String
let powerPs: String
let milage: String
let fueltype: String }
class OfferVC: UIViewController {
#IBOutlet weak var tableView: UITableView!
var viewModels = [CarItems]()
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "http://grandex.de/api/v1/de/offers"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, error) in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(OfferList.self, from: data!)
self.viewModels = result.data
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}.resume()
}
}
extension OfferVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id") as! Cell
let vm = viewModels[indexPath.row]
cell.update(with: vm)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 130
} }
class Cell: UITableViewCell {
#IBOutlet weak var carImage: UIImageView!
#IBOutlet weak var title: UILabel!
#IBOutlet weak var fueltype: UILabel!
func update(with item: CarItems) {
title?.text = item.manufacturer
fueltype?.text = item.fueltype
carImage?.image = item.image
}
func getImage(with item: CarItems){
let session = URLSession(configuration: URLSessionConfiguration.default)
guard let url = URL(string: "\(item.image)") else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
session.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Something went wrong: \(error)")
}
if let imageData = data {
DispatchQueue.main.async {
self.carImage.image = UIImage(data: data!)
}
}
}.resume()
} }
I'm expecting it to add images to my cells, but it doesn't work.
Any help would be much appreciated! Thanks in advance!
First of all, in CarItems struct, set the type of image as String instead of URL. You get a String from JSON and not a URL type.
In update(with:) method, call getImage(with:) method, i.e.
func update(with item: CarItems) {
title?.text = item.manufacturer
fueltype?.text = item.fueltype
self.getImage(with: item) //Here..
}
Also, keep the code as short as possible. You can cut down the getImage(with:) method to:
func getImage(with item: CarItems){
guard let url = URL(string: "\(item.image)") else { return }
URLSession.shared.dataTask(with: url) {[weak self] (data, response, error) in
if let data = data {
DispatchQueue.main.async {
self?.carImage.image = UIImage(data: data)
}
}
}.resume()
}
my collection view don't show gifs.. im using GIPHY.. and SwiftGif Extension, to show the gifs on UIImageView... this is the code
func searchGif(search: String) {
GiphyCore.configure(apiKey: "hRuR15WOxvhonLAsLhd0R8pDGvJxQYOk")
respondView.isHidden = true
_ = GiphyCore.shared.search(search, media: .gif, offset: 2, limit: 6, rating: .ratedG, lang: .english, completionHandler: { [weak self] (response, error) in
self?.isDataLoading = false
if let error = error {
print("error in response", error)
}
guard
let data = response?.data else { return }
self?.initialize()
for results in data {
let urlString = results.url
guard let url = URL(string: urlString) else { return }
do {
let data = try Data(contentsOf: url)
let foundedGif = GifModel(gifUrl: data, urlString: urlString)
self?.gifModel.append(foundedGif)
} catch let error as NSError {
print(error)
}
}
if self?.gifModel.isEmpty ?? false {
self?.setupNofound()
}
DispatchQueue.main.async {
self?.gifCollectionView.reloadData()
}
})
}
in the delegates on collection view...
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: GifSearchCollectionViewCell.identifier,
for: indexPath
) as? GifSearchCollectionViewCell else {
return UICollectionViewCell()
}
cell.gifModel = gifModel[indexPath.row]
return cell
}
and in the numberOfItems in sections as well .....
I put gifModel.count the data works good, I have a response with the 6 on the array model...
and in the cell:
#IBOutlet weak var splashGifView: UIImageView!
var gifModel: GifModel? {
didSet {
guard
let gif = gifModel?.gifUrl else { return }
splashGifView.image = UIImage.gifImageWithData(gif)
}
}
I tried with String but nothing, the cells already create, but don't show the gifs... someone can help?
update...
#IBOutlet weak var splashGifView: UIImageView!
var gifModel: GifModel? {
didSet {
guard let gif = gifModel? { return }
let url = gif.gifUrl. // <- this give nill
splashGifView.image = UIImage.gifImageWithData(url)
}
}
the url have nill, but in my model I have the data url correctly...
I figured out!.. GIPHY, have a struct very "inbound", I get the image Gif inside of the response like this....
results.images?.original?.gifUrl
for results in data {
let newGif = GifModel(gifUrl: results.images?.original?.gifUrl ?? "", run: false)
self?.gifModel.append(newGif)
}
and now I can get the url with the extension ".GIF" and with that SwiftGif can show on the collectionView the gifs...
I have been working on a launch database for SpaceX and I have successfully parsed my data but the function to create and add the data to the cell is not working. I have added the delegates and data sources but I still cannot find out why it won't run.
import UIKit
struct launchData : Decodable
{
let flight_number : Int
let launch_date_utc : String
struct rocketInfo : Decodable
{
let rocket_name : String
}
let rocket : rocketInfo
}
class LaunchViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var launchTableView: UITableView!
var arrayOfLaunchData:[launchData] = []
override func viewDidLoad()
{
super.viewDidLoad()
self.launchTableView.delegate = self
self.launchTableView.dataSource = self
getJsonData()
self.launchTableView.reloadData()
}
func getJsonData()
{
let jsonUrlString = "https://api.spacexdata.com/v2/launches"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let launchDataDecoded = try JSONDecoder().decode([launchData].self, from: data)
print(launchDataDecoded)
} catch let jsonErr {
print("Error Serialization json:", jsonErr )
}
}.resume()
print("getJsonData ran")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("")
print(arrayOfLaunchData.count)
print("")
print("TableView number of rows ran")
return arrayOfLaunchData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellID")
let launch = self.arrayOfLaunchData[indexPath.row]
let flightNumber = launch.flight_number
let rocketName = launch.rocket.rocket_name
cell?.textLabel?.text = "Mission " + String(flightNumber)
let launchDate = launch.launch_date_utc
cell!.detailTextLabel!.text = "Launch Date: " + launchDate + "Rocket Used: " + rocketName
self.launchTableView.reloadData()
print("TableView cellForRowAt ran")
return cell!
}
}
First of all never call reloadData() in cellForRowAt! Delete the line
Two major issues:
reloadData() is called too soon.
The data source array is not populated after receiving the data.
The solution is to delete the line
self.launchTableView.reloadData()
(also) in viewDidLoad() and change getJsonData() to
func getJsonData()
{
let jsonUrlString = "https://api.spacexdata.com/v2/launches"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
self.arrayOfLaunchData = try JSONDecoder().decode([launchData].self, from: data)
print(launchDataDecoded)
DispatchQueue.main.async {
self.launchTableView.reloadData()
}
} catch {
print("Error Serialization json:", error )
}
}.resume()
print("getJsonData ran")
}
because dataTask works asynchronously.
Note:
Please conform to the naming convention that struct and class names start with a capital letter (LaunchData, RocketInfo) and all names are supposed to be camelCased rather than snake_cased.
Remove self.launchTableView.reloadData() from viewDidLoad()
and put on getting successfully data
do {
let launchDataDecoded = try JSONDecoder().decode([launchData].self, from: data)
print(launchDataDecoded)
self.launchTableView.reloadData()
} catch let jsonErr {
print("Error Serialization json:", jsonErr )
}
}.resume()
getJsonData() is follow asynchronous. hope this help!