How i implement my JSON API Request into a UITableViewCell? - swift

I have a problem with my current Project. First of all, i like to implement a JSON API Request that allows me to get a title off a URL. The Problem: I want to display the JSON data into a UITableViewCell.
But Xcode throws following Error:
Cannot assign value of type 'FirstViewController.Title' to type
'String?'
Maybe there is more wrong in my code, because i'm just a beginner at Swift/Xcode
I already tried this:
cell.textLabel?.text = course.title as? String
But i got warning message as follows:
Cast from 'FirstViewController.Title' to unrelated type 'String' always fails
This is my code sample:
var courses = [Course]()
let cell = "ItemCell"
override func viewDidLoad() {
super.viewDidLoad()
fetchJSON()
}
struct Course: Codable {
let title: Title
enum CodingKeys: String, CodingKey {
case title
case links = "_links"
}
}
struct Links: Codable {
}
struct Title: Codable {
let rendered: String
}
fileprivate func fetchJSON() {
let urlString = "ExampleURL"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
DispatchQueue.main.async {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
let result = try JSONDecoder().decode(Course.self, from: data)
self.tableView.reloadData()
} catch let jsonErr {
print("Failed to decode:", jsonErr)
}
}
}.resume()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return courses.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .value1, reuseIdentifier: "ItemCell")
let course = courses[indexPath.row]
cell.textLabel?.text = course.title as? String // Cast from 'FirstViewController.Title' to unrelated type 'String' always fails
return cell
}
I just want to get WordPress posts into a UITableView - UITableViewCell.
Maybe you can tell me if its the wrong way i tried it but i don't really know how i solve this problem
Thank you in advance

Assign the var before the reload
let res = try JSONDecoder().decode(Course.self, from: data)
courses.append(res)
DispatchQueue.main.async {
self.tableView.reloadData()
}
And set it to the string value
cell.textLabel?.text = course.title.rendered
courses = try JSONDecoder().decode([Course].self, from: data)
print(courses)

Related

How to get value from the first 3 rows in TableView using swift?

I retrieve data from MySql via PHP file to get users information and scores to load them in a table. I need to get the value of the first 3 users and put them in a Label outside the Table, it is like game leaders list. I attached an image to explain the idea.
Here is the structure code:
import Foundation
protocol HomeModelProtocol: AnyObject {
func itemsDownloaded(items: NSArray)
}
class HomeModel: NSObject, URLSessionDataDelegate {
weak var delegate: HomeModelProtocol!
let urlPath = "https://mywebsite.com/folder/callUserList.php" //this will be changed to the path where service.php lives
func downloadItems() {
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
let users = NSMutableArray()
for i in 0 ..< jsonResult.count
{
jsonElement = jsonResult[i] as! NSDictionary
let user = UsersModel()
//the following insures none of the JsonElement values are nil through optional binding
if let name = jsonElement["name"] as? String,
let email = jsonElement["email"] as? String,
let phoneNumber = jsonElement["phone"] as? String,
let userImage = jsonElement["image"] as? String
{
user.name = name
user.email = email
user.phoneNumber = phoneNumber
user.userImage = userImage
}
users.add(user)
}
DispatchQueue.main.async(execute: { () -> Void in
self.delegate.itemsDownloaded(items: users)
})
}
}
Here is the model:
import Foundation
class UsersModel: NSObject {
//properties
var name: String?
var email: String?
var phoneNumber: String?
var userImage: String?
//empty constructor
override init()
{
}
//construct with #name, #address, #latitude, and #longitude parameters
init(name: String, email: String, phoneNumber: String, userImage: String) {
self.name = name
self.email = email
self.phoneNumber = phoneNumber
self.userImage = userImage
}
//prints object's current state
override var description: String {
return "Name: \(String(describing: name)), Email: \(String(describing: email)), Phone Number: \(String(describing: phoneNumber)), User Image: \(String(describing: userImage))"
}
}
Here is the code in the TableView controller:
var feedItems: NSArray = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
let homeModel = HomeModel()
homeModel.delegate = self
homeModel.downloadItems()
}
func itemsDownloaded(items: NSArray) {
feedItems = items
self.listTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve cell
let cellIdentifier: String = "BasicCell"
let myCell: WinnerTableCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! WinnerTableCell
// Get the location to be shown
let item: UsersModel = feedItems[indexPath.row] as! UsersModel
// Get references to labels of cell
myCell.lbTextName!.text = item.name
return myCell
}
The data shows in the Table but I have no idea how to fill the 3 label with the 3 first users from the Table.
How can I get these values from the table and pass it to a label in the same ViewController?
Thanks
When adding this code:
if feedItems.count >= 3 {
lblFirstWinner.text = feedItems[0].name // 1st winner
lblSecondWinner.text = feedItems[1].name // 2nd winner
lblThirdWinner.text = feedItems[2].name // 3rd winner
}
it shows error: Value of type 'Any' has no member 'name'
Change itemsDownloaded method as
func itemsDownloaded(items: NSArray) {
feedItems = items
self.listTableView.reloadData()
for (index, user) in items.enumerated() {
let user = user as! UserModel
switch index {
case 0: // 1st winner
lblFirstWinner.text = user.name
case 1: // 2nd winner
lblSecondWinner.text = user.name
case 2: // 3rd winner
lblThirdWinner.text = user.name
}
}
}
OR
Change your HomeModelProtocol method and feedItems type to [UsersModel]
protocol HomeModelProtocol: AnyObject {
func itemsDownloaded(items: [UsersModel]) // Changed
}
var feedItems =[UsersModel]() // Changed
override func viewDidLoad() {
super.viewDidLoad()
let homeModel = HomeModel()
homeModel.delegate = self
homeModel.downloadItems()
}
func itemsDownloaded(items: [UsersModel]) {
feedItems = items
self.listTableView.reloadData()
if feedItems.count >= 3 {
lblFirstWinner.text = feedItems[0].name // 1st winner
lblSecondWinner.text = feedItems[1].name // 2nd winner
lblThirdWinner.text = feedItems[2].name // 3rd winner
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve cell
let cellIdentifier: String = "BasicCell"
let myCell: WinnerTableCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! WinnerTableCell
// Get references to labels of cell
myCell.lbTextName!.text = feedItems[indexPath.row].name // Changed
return myCell
}
Just you need to add a few lines in the below function and your solution will be done.
func itemsDownloaded(items: NSArray) {
feedItems = items
self.listTableView.reloadData()
if feedItems.count >= 3 {
lblFirstWinner.text = feedItems[0].name // 1st winner
lblSecondWinner.text = feedItems[1].name // 2nd winner
lblThirdWinner.text = feedItems[2].name // 3rd winner
}
}
Let me know... is it working for you? and please also refer to #vadian comment on your question.

Saving TableView cell using UserDefaults

I'm trying to get cell of tableView using UserDefaults, but after i reload app it is always empty
This is my Model:
struct Note: Codable {
var title: String
var description: String
}
class Notes {
var stock: [Note] = []
}
View contoller
var model = Notes()
This is how i get data
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "TableViewCell")
tableView.reloadData()
if let fetchedData = UserDefaults.standard.data(forKey: "notes") {
let fetchedBookies = try! PropertyListDecoder().decode([Note].self, from: fetchedData)
print(fetchedBookies)
} else {
model.stock = []
}
}
This is my cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell
cell.titleOutlet.text = self.model.stock[indexPath.row].title
cell.descriptionOutlet?.text = self.model.stock[indexPath.row].description
return cell
}
How i save data
#IBAction func check(_ sender: Any) {
let newstock = Note(title: "check", description: "check2")
model.stock.append(newstock)
print(model.stock.count)
let bookiesData = try! PropertyListEncoder().encode(model.stock)
UserDefaults.standard.set(bookiesData, forKey: "notes")
tableView.reloadData()
}
Thank you very much!
I recommend you to use Json Encoder/Deocder.
First set your Notes class to conform to Codable:
class Notes: Codable {
var stock: [Note] = []
}
Here is an example of how to use Json Encoder / Decoder:
func save(notes: Notes) throws {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(notes)
UserDefaults.standard.set(data, forKey: "notes")
} catch let error {
throw error
}
}
func load() -> Notes {
guard let data = UserDefaults.standard.data(forKey: "notes") else {
return Notes() // Default
}
let decoder = JSONDecoder()
do {
let object = try decoder.decode(Notes.self, from: data)
return object
} catch {
return Notes() // Default
}
}
In your code just call load() to get your notes from User Defaults
And save(notes:) to save them into User Defaults.

Index out of range when presenting JSON data in tableview

I am having issue identifying and changing the color of tableview rows that contain the same name value in both [ListStruct] which contains the inital data for the tableview rows, and [HighlightStruct] which contains the name that need to be highlighted.
Initially I have the following JSON array populate my tableview:
private func fetchJSON() {
guard let url = URL(string: "www.test.com")
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "test=test1".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
self.structure = try JSONDecoder().decode([ListStruct].self,from:data)
DispatchQueue.main.async {
self.tableView.reloadData()
}}catch {print(error)}}.resume()}
struct ListStruct: Codable {
let id: String
let wo: String
let name: String
let type: String
}
Then the same view controller has a second JSON array that is decoded below for highlighting:
func processJSON(_ json: String) {
do{
let mydata = Data(json.utf8)
let decoded = try JSONDecoder().decode(Set<HighlightStruct>.self,from: mydata)
print(decoded)
} catch {
print(error)
}
}
struct HighlightStruct: Codable, Hashable {
var id: Int
var name: String
}
Applying Highlight
var mySet: Set<HighlightStruct> = []
var highlightedStructure = [HighlightStruct]()
var structure = [ListStruct]()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as! myCell
let portfolio: ListStruct
portfolio = structure[indexPath.row]
let highlight: HighlightStruct
highlight = highlightedStructure[indexPath.row]
//Highlight those that match in both arrays
if highlight.wo == portfolio.wo {
cell.backgroundColor = .yellow
}
Getting index out of range
You are getting index out of range error because your arrays are empty or there is no index that exist in your arrays. Maybe you can check your service call, the arrays could not be filled properly.
Make sure ur two list count is same size, or process data to one list.
You need to handle exceptions when structure does not have same wo to compare.
struct ListStruct: Codable {
let id: String
let wo: String
let name: String
let type: String
let hightlight:HighlightStruct!
}
func processJSON(_ json: String) {
do{
let mydata = Data(json.utf8)
let decoded = try JSONDecoder().decode(Set<HighlightStruct>.self,from: mydata)
print(decoded)
for hl in decoded{
var filter = structure.filter({$0.wo == hl.wo})
filter.hightlight = hl
}
} catch {
print(error)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as! myCell
let portfolio: ListStruct
portfolio = structure[indexPath.row]
//Highlight those that match in both arrays
if portfolio.hightlight?.wo == portfolio.wo {
cell.backgroundColor = .yellow
}

Bind Alamofire request to table view using RxSwift

So I have been researching RxSwift for a couple days, and am trying to create a simple app with it. I have bound the searchController of my table to the results, which feed into the cellForRowAt function. How do I bind the alamofire response to each cell?
Which of these do I need to do?
Use RxAlamofire to create an searchResultsArray
Change searchResultsArray to a Variable and use toObservable?
Bind response or searchResultsArray to create each cell.
The function I need to use is:
.bind(to: self.tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) { row, element, cell in
cell.textLabel?.text = "something"
}
This is my current RxSwift code:
let disposeBag = DisposeBag()
var searchResultsArray = [[String:String]]()
searchController.searchBar.rx.text.orEmpty.filter { text in
text.count >= 3
}.subscribe(onNext: { text in
searchRequest(search: text, searchType: "t:t") { response in
self.searchResultsArray = response
self.tableView.reloadData()
}
}).disposed(by: disposeBag)
This is my current cell creation function. showSearchResults changes when the cancel button is clicked.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
return UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
}
return cell
}()
if self.shouldShowSearchResults {
cell.textLabel?.text = searchResultsArray[indexPath.row]["result"]!
cell.detailTextLabel?.text = searchResultsArray[indexPath.row]["location"]!
}
return cell
}
This is my current api request:
func searchRequest(search: String, searchType: String, completionHandler: #escaping ([[String: String]]) -> ()) {
let payload: [String: Any] = [
"q": search,
"fq": searchType,
"start": 0
]
let url = URL(string: "https://www.athletic.net/Search.aspx/runSearch")!
Alamofire.request(url, method: .post, parameters: payload, encoding: JSONEncoding.default).responseJSON { response in
let json = response.data
do {
var searchResults: [[String: String]] = []
let parsedJson = JSON(json!)
if let doc = try? Kanna.HTML(html: parsedJson["d"]["results"].stringValue, encoding: .utf8) {
for row in doc.css("td:nth-child(2)") {
let link = row.at_css("a.result-title-tf")!
let location = row.at_css("a[target=_blank]")!
let schoolID = link["href"]!.components(separatedBy: "=")[1]
searchResults.append(["location": location.text!, "result": link.text!, "id":schoolID])
}
}
completionHandler(searchResults)
} catch let error {
print(error)
}
}
}
I would like to replace the cellForRowAt with a RxSwift solution.
Based on the code you presented, use of Rx will give you something like this:
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchBar.rx.text.orEmpty
.filter { text in text.count >= 3 }
.flatMapLatest { text in searchRequest(search: text, searchType: "t:t") }
.bind(to: self.tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) { row, element, cell in
if self.shouldShowSearchResults {
cell.textLabel?.text = element["result"]!
cell.detailTextLabel?.text = element["location"]!
}
}
.disposed(by: disposeBag)
}
The shouldShowSearchResults feels out of place in that. But otherwise it looks good.
The above assumes you wrap your searchRequest in a function that returns an observable like this:
func searchRequest(search: String, searchType: String) -> Observable<[[String: String]]> {
return Observable.create { observer in
searchRequest(search: search, searchType: searchType, completionHandler: { result in
observer.onNext(result)
observer.onCompleted()
})
return Disposables.create()
}
}
The above is a standard pattern that wraps a function that uses a callback into a function that returns an Observable.

How to write my Struct to Firebase

I have this struct:
struct Info {
var name: String = ""
var number = Int()
}
var infoProvided : [Info] = []
I display desired data in a tableView:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "InfoCell") as! InfoTableViewCell
let name = infoProvided[indexPath.row].name
let number = infoProvided[indexPath.row].number
cell.infoLabelLabel.text = "\(name) with \(number)"
return cell
}
I am trying to write data to firebase like this:
self.ref?.child(gotLokasjon).child(location).child(dateAndTime).updateChildValues(["Signed up:" : infoProvided])
This returns the error:
Cannot store object of type _SwiftValue at 0. Can only store objects of type NSNumber, NSString, NSDictionary, and NSArray.'
How can I write my Struct to Firebase?. I would like to write it equal to how its displayed in the tableView:
cell.infoLabelLabel.text = "\(name) with \(number)"
I haven't understood where you want the upload to happen(before or after they are displayed on tableView) so adjust it to suit your needs.
guard let name = infoProvided.name else {return}
guard let number = infoProvided.number else {return}
let data = [ "name": name, "number": number] as [String : Any]
self.ref?.child(gotLokasjon).child(location).child(dateAndTime).updateChildValues(data, withCompletionBlock: { (error, ref) in
if error != nil{
print(error!)
return
}
print(" Successfully uploaded")
})
After a bit of fiddling I did this:
let infoArray = infoProvided.map { [$0.number, $0.name] }
let items = NSArray(array: infoArray)
Then implemented that in the above solution. This seams to work.
I don't know if this is a good solution?