I'm want to use some custom struct in my collection view cell. I get the data from my API service and trying to pass it to my custom collection view cell.
I found couple answers but I still couldn't figure out how to do
Here is where I get the actual data:
func FetchFormData(linkUrl: String) {
let parameters: [String: AnyObject] = [:]
let postString = (parameters.flatMap({ (key, value) -> String in
return "\(key)=\(value)"
}) as Array).joined(separator: "&")
let url = URL(string: linkUrl)!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
let responseString = String(data: data, encoding: .utf8)
let contentData = responseString?.data(using: .utf8)
do {
let decoder = JSONDecoder()
self.formData = try decoder.decode(FormModel.self, from: contentData!)
} catch let err {
print("Err", err)
}
DispatchQueue.main.async {
//here is the where I reload Collection View
self.collectionView.reloadData()
}
}
task.resume()
}
Also here I'm trying to pass data to the cell:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! BaseFormCollectionViewCell
cell.backgroundColor = .green
//Data could be print out here
print(self.formData?.components?[indexPath.row])
cell.formComponent = (self.formData?.components?[indexPath.row])!
return cell
}
The actual problem is starting into my cell class
class BaseFormCollectionViewCell: UICollectionViewCell {
var formComponent: FormComponent!{
didSet {
//data can be print out here
print("Passed value is: \(formComponent)")
}
}
override init(frame: CGRect) {
super.init(frame: frame)
//this part is always nill
print(formComponent)
}
}
As you guys can see in the code It's going well until my collection view cell.
It should be a lot more simple but I couldn't figure out what's going on and Why its happening.
Modify your cell class as
class BaseFormCollectionViewCell: UICollectionViewCell {
var formComponent: FormComponent!{
didSet {
//this is unnecessary. You can achieve what u want with a bit more cleaner way using configure function as shown below
//data can be print out here
print("Passed value is: \(formComponent)")
}
}
override init(frame: CGRect) {
super.init(frame: frame)
//this part is always nill
print(formComponent)
}
func configure() {
//configure your UI of cell using self.formComponent here
}
}
finally
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! BaseFormCollectionViewCell
cell.backgroundColor = .green
//Data could be print out here
print(self.formData?.components?[indexPath.row])
cell.formComponent = (self.formData?.components?[indexPath.row])!
(cell as! BaseFormCollectionViewCell).configure()
return cell
}
Look for (cell as! BaseFormCollectionViewCell).configure() in cellForItemAt thats how u trigger the UI configuration of cell after passing data to cell in statement above it.
Quite frankly u can get rid of didSet and relay on configure as shown
Hope it helps
Related
i am making app with CollectionView cells using Swift and i fetching posts from my WordPress Website, i want to show posts in CollectionView cell and i want to show full text in Label, but the problem is that when is show posts on CollectionView , scroll is not smooth and sometimes it just stop scrolling for some seconds, this is my code to fetch posts..
func fetchPostData(completionHandler: #escaping ([Post]) -> Void ) {
let url = URL(string: "https://www.sikhnama.com/wp-json/wp/v2/posts/?categories=5&per_page=30&page=\(page)\(sortBy)")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {return}
do {
let postsData = try JSONDecoder().decode([Post].self, from: data)
completionHandler(postsData)
DispatchQueue.main.async {
self.collectionView.reloadData()
SVProgressHUD.dismiss()
}
}
catch {
let error = error
print(String(describing: error))
}
}
task.resume()
}
this is in my CollectionViewCell
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
return layoutAttributes
}
and this is how i convert html to text
titleLabel.text = String(htmlEncodedString: hukam.content.rendered)
this is in Viewdid load
let layout = collectionView?.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = UICollectionViewFlowLayout.automaticSize
layout.estimatedItemSize = CGSize(width: view.frame.width-20, height: 40)
this is collectionView Extension
extension StatusViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
return newsData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "postcell", for: indexPath) as! StatusViewCell
cell.setup(with: newsData[indexPath.row])
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale
return cell
}
and this is how i setup constrain of label
this is my cpu profiler in Instruments
this is how i convert html to text
extension String {
init(htmlEncodedString: String) {
self.init()
guard let encodedData = htmlEncodedString.data(using: .utf8) else {
self = htmlEncodedString
return
}
let attributedOptions: [String : Any] = [
convertFromNSAttributedStringDocumentAttributeKey(NSAttributedString.DocumentAttributeKey.documentType): convertFromNSAttributedStringDocumentType(NSAttributedString.DocumentType.html),
convertFromNSAttributedStringDocumentAttributeKey(NSAttributedString.DocumentAttributeKey.characterEncoding): String.Encoding.utf8.rawValue
]
do {
let attributedString = try NSAttributedString(data: encodedData, options: convertToNSAttributedStringDocumentReadingOptionKeyDictionary(attributedOptions), documentAttributes: nil)
self = attributedString.string
} catch {
print("Error: \(error)")
self = htmlEncodedString
}
}
}
fileprivate func convertFromNSAttributedStringDocumentAttributeKey(_ input: NSAttributedString.DocumentAttributeKey) -> String {
return input.rawValue
}
fileprivate func convertFromNSAttributedStringDocumentType(_ input: NSAttributedString.DocumentType) -> String {
return input.rawValue
}
fileprivate func convertToNSAttributedStringDocumentReadingOptionKeyDictionary(_ input: [String: Any]) -> [NSAttributedString.DocumentReadingOptionKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (NSAttributedString.DocumentReadingOptionKey(rawValue: key), value)})
}
I made a new application using tableView, there were cuts at the end of the descriptions of the news in my application, so I chose the label from the main Storyboard menu and made Autoshirink as Minimum font scale, I reduced it by 0.5 and it worked.
don't forget to make the lines zero because zero means you have infinite lines
This is how I parsed the data from JSON. I created a model, I put that model in an empty array here, then I added the data I parsed into my array, then when I show it in the cell, I print it according to the indexPath.row of this array.
func fetchNews(){
let urlRequest = URLRequest(url: URL(string: "https://inshorts.deta.dev/news?category="+categoryId)!)
loading.startAnimating()
let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if error != nil {
print(error?.localizedDescription)
return
}
do{
let json = try JSONSerialization.jsonObject(with: data!) as! [String : AnyObject]
if let articklesFromJson = json["data"] as? [[String : AnyObject]]{
for articleFromJson in articklesFromJson {
let article = NewsModel()
if let title = articleFromJson["title"] as? String, let author = articleFromJson["author"] as? String, let content = articleFromJson["content"] as? String, let imageUrl = articleFromJson["imageUrl"] as? String {
article.author = author
article.content = content
article.title = title
article.imageUrl = imageUrl
}
self.News?.append(article)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}catch {
print("error")
}
}
task.resume()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! tableCell
cell.titleCell.text = self.News?[indexPath.item].title
cell.authorCell.text = self.News?[indexPath.item].author
cell.textCell.text = self.News?[indexPath.item].content
cell.imgCell.dowloadImage(from: (self.News![indexPath.item].imageUrl!))
loading.stopAnimating()
return cell
}
I tested the collection view can displayed the content. However, I can't retrieve and add array result in the collection view.
Here is my code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MainPageCollectionViewCell
cell.FoodTitle.text = arr[indexPath.row].title
cell.Food.image = arr[indexPath.row].image_url
return cell
}
About the array, it is a function inside the fetchFoodList.
func fetchFoodList(){
let url = URL(string: "MYAPI.json")
guard let unwrappedUrl = url else { return }
let task = URLSession.shared.dataTask(with: unwrappedUrl, completionHandler: {(data, response, error)in
if error != nil{
print(error!)
} else {
if let urlContent = data{
do {
let json = try JSON(data:data!)
let recipes = json["recipes"]
for arr in recipes.arrayValue{
print(arr["title"])
print(arr["image_url"])
}
}
catch{
print("JSON Processing Failed")
}
}
}
})
task.resume()
}
}
However, the array result title and image_url can displayed in the console.
After appending the array list need to reload collection like this
collectionView.reloadData()
I want to know how to get each name from the parsed json response in swift. I want to assign the name value of the reponse to cell.label.text based on my code cell.label.text = item.name. Label will automate depending on how many item.name , what I want is that it will automate depending on how many name there is from json response
if indexPath.row == 0 {
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "AddNewCell", for: indexPath as IndexPath) as! AddNewCollectionViewCell
cell.backgroundColor = unselectedCellColor // make cell more visible in our example project
return cell
} else {
let index = IndexPath(row: (indexPath.row - 1), section: 0)
let item = fetchedResultsController.object(at: index)
// get a reference to our storyboard cell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! GroceryItemCollectionViewCell
cell.buttonDelegate = self
cell.deleteButton.tag = indexPath.row - 1
cell.label.text = item.name
if item.isSelected {
cell.backgroundColor = selectedCellColor
} else {
cell.backgroundColor = unselectedCellColor
}
return cell
}
Http request code:
responseString = Optional("[{\"id\":51,\"name\":\"jelord\",\"desc\":\"ako si jelord\",\"reward\":\"1.00\",\"sched\":\"2018-04-06T11:37:09+08:00\",\"parent\":null,\"child\":null,\"occurrence\":{\"name\":\"once\"},\"status\":\"created\"},{\"id\":53,\"name\":\"uuuuuu\",\"desc\":\"uuuuu\",\"reward\":\"8.00\",\"sched\":\"2018-03-06T10:49:54+08:00\",\"parent\":null,\"child\":null,\"occurrence\":{\"name\":\"once\"},\"status\":\"created\"},{\"id\":54,\"name\":\"iiiii\",\"desc\":\"oiii\",\"reward\":\"67.00\",\"sched\":\"2018-02-06T10:51:34+08:00\",\"parent\":null,\"child\":null,\"occurrence\":{\"name\":\"once\"},\"status\":\"created\"},{\"id\":55,\"name\":\"uuuu\",\"desc\":\"uuuu\",\"reward\":\"8.00\",\"sched\":\"2018-03-06T10:52:55+08:00\",\"parent\":null,\"child\":null,\"occurrence\":{\"name\":\"once\"},\"status\":\"created\"},{\"id\":57,\"name\":\"uuuuuuuu\",\"desc\":\"uuuuuu\",\"reward\":\"8888.00\",\"sched\":\"2018-04-06T11:54:16.431000+08:00\",\"parent\":null,\"child\":null,\"occurrence\":{\"name\":\"once\"},\"status\":\"created\"},{\"id\":61,\"name\":\"hhu\",\"desc\":\"yhh\",\"reward\":\"67.00\",\"sched\":\"2018-02-06T13:45:09+08:00\",\"parent\":null,\"child\":null,\"occurrence\":{\"name\":\"once\"},\"status\":\"created\"},{\"id\":62,\"name\":\"huhu\",\"desc\":\"huu\",\"reward\":\"8.00\",\"sched\":\"2018-04-06T14:46:36.620000+08:00\",\"parent\":null,\"child\":null,\"occurrence\":{\"name\":\"once\"},\"status\":\"created\"}]")
code for getting the request.
var request = URLRequest(url: URL(string: "http://test.test:8000/api/v1/test/")!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(String(describing: responseString))")
}
task.resume()
import UIKit
import Alamofire
class MenuCollectionViewController: UIViewController,
UICollectionViewDelegate, UICollectionViewDataSource {
var titleArray = [String]()
#IBOutlet var collectionView: UICollectionView!
#IBAction func signOutButtonIsPressed(_ sender: Any) {
let appDelegate : AppDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.showLoginScreen()
}
#IBOutlet var signoutButton: UIButton!
var items = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
self.signoutButton.layer.cornerRadius = 3.0
demoApi()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.navigationBar.isHidden = true
self.navigationItem.hidesBackButton = true
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return titleArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionCell
cell.nameLabel.text = titleArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
}
func demoApi() {
Alamofire.request("https://jsonplaceholder.typicode.com/posts", method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON { (response:DataResponse<Any>) in
switch(response.result) {
case .success(_):
guard let json = response.result.value as! [[String:Any]]? else{ return}
print("Response \(json)")
for item in json {
if let title = item["title"] as? String {
self.titleArray.append(title)
}
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
break
case .failure(_):
print("Error")
break
}
}
}
}
class CollectionCell: UICollectionViewCell {
#IBOutlet weak var imgPhoto: UIImageView!
#IBOutlet weak var nameLabel: UILabel!
}
So I'm trying to parse some JSON data retrieved from a server and display it nicely in a table, ive followed the suggestions here: UITableView example for Swift and managed to get the example working.
However with the data im parsing the table remains blank.
Can anyone see where im going wrong?
import UIKit
import SwiftyJSON
class LogsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let animals: [String] = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
var arrayCount:Int = 0
struct Item {
let name : String
let lockTime : String
let type : String
}
// cell reuse id (cells that scroll out of view can be reused)
let cellReuseIdentifier = "cell"
var items = [Item]()
#IBOutlet weak var textUodate: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
// Register the table view cell class and its reuse id
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
tableView.delegate = self
tableView.dataSource = self
// Do any additional setup after loading the view.
let lockid = UserDefaults.standard.value(forKey: "LockID")!
// let email = UserDefaults.standard.value(forKey: "email")!
//get the data from the server for that specific lock id
let u = UserDefaults.standard.value(forKey: "userIP")!
var request = URLRequest(url: URL(string: "http://\(u):3000/logs")!)
request.httpMethod = "POST"
let postString = "LockID=\(lockid)"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
print(response ?? " ")
}
let responseString = String(data: data, encoding: .utf8)
if let data = responseString?.data(using: String.Encoding.utf8) {
let resString = JSON(data: data)
if resString["success"].stringValue == "true"
{
self.arrayCount = (resString["message"].count)
print(self.arrayCount)
let returnedArray = resString["message"].arrayValue
for item in returnedArray {
let name = String(describing: item["name"])
let lockTime = String(describing: item["lockTime"])
let type = String(describing: item["type"])
self.items.append(Item(name:name, lockTime:lockTime, type:type))
}
}
else if resString["success"].stringValue == "false"
{
print(resString["success"].stringValue)
}
}
}
task.resume()
DispatchQueue.main.async{
self.tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayCount
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
let item = items[indexPath.row]
let cellText = "\(item.name) \(item.type) At: \(item.lockTime) "
cell.textLabel?.text = cellText
print(cellText)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
}
Data is not displaying because you didn't reload the table view after you append the items array. Please reload the table view at the end of task closure
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// ......
// ......
self.tableView.reloadData()
}
I´m building a widget for iOS with Swift. The main app´s purpose is to connect to a URL news feed and get the latest news, while the widget only get the title to display in a tableView in the Today view.
I´ve written this method for the widget in order to get the data to populate the table, but for some reason nothing is showing. I´ve tried to debug it, but being a widget it seems to be practically imposible.
This is the cellForRowAt, where I connect to the feed and try to extract data. The funny part is, the main app uses basically the same code and it works perfectly.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let urlRequest = URLRequest(url: URL(string: "https://newsapi.org/v1/articles?source=techcrunch&sortBy=top&apiKey=c64849bc30eb484fb820b80a136c9b0a")!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
if let articlesFromJson = json["articles"] as? [[String: AnyObject]] {
if !(error != nil) {
var resultArray: NSMutableArray = NSMutableArray()
for articlesFromJson in articlesFromJson {
if let title = articlesFromJson["title"] as? String{
resultArray.add(title)
}
let array:NSArray = resultArray.reverseObjectEnumerator().allObjects as NSArray
resultArray = array as! NSMutableArray
let title:String = resultArray.object(at: indexPath.row) as! String
cell.textLabel?.text = title
}
}
}
//reload on main thread to speed it up
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error {
print(error)
}
}
task.resume()
return cell
}
If someone can help me figure out where is the mistake it would be a huge help, i´ve been stuck on this issue for days now. Thanks
You want to make your network request outside of cellForRow and then reloadData once it's complete to have the tableView reload the cells which calls cellForRow.
store the array of data outside of request so you can reference it from outside the function.
var resultArray: NSMutableArray = []
override func viewDidLoad() {
super.viewDidLoad()
getData()
}
func getData() {
let urlRequest = URLRequest(url: URL(string: "https://newsapi.org/v1/articles?source=techcrunch&sortBy=top&apiKey=c64849bc30eb484fb820b80a136c9b0a")!)
let task = URLSession.shared.dataTask(with: urlRequest) {[weak self] (data,response,error) in
guard let strongSelf = self else { return }
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: AnyObject]
if let articlesFromJson = json["articles"] as? [[String: AnyObject]] {
if error == nil {
for articlesFromJson in articlesFromJson {
if let title = articlesFromJson["title"] as? String{
strongSelf.resultArray.add(title)
}
let array:NSArray = strongSelf.resultArray.reverseObjectEnumerator().allObjects as NSArray
strongSelf.resultArray = array as! NSMutableArray
DispatchQueue.main.async {
strongSelf.tableView.reloadData()
}
} catch let error {
print(error)
}
}
task.resume()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let title:String = resultArray.object(at: indexPath.row) as! String
cell.textLabel?.text = title
return cell
}
checks proper if TableView delegate or datasource proper connected. and check array count before load data in cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if (resultArray.count > 0){
let title:String = resultArray.object(at: indexPath.row) as! String
cell.textLabel?.text = title
}
else
{
print("Error: resultArray contain nil value ")
}
return cell
}