I'm getting data from Reddit API and displaying reddit posts in the tableview. I want to load 5 (for example) posts from api at a time and display them, and then load next 5 posts when the user scroll down to the last fifth cell.
Maybe I need to change my logic of getting posts from api. Please advice. Thank you!
I'm using MVVM. Populate tableview cells with posts data in TableViewController:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell") as! PostTableViewCell
vm.getPost(at: indexPath.row) { post in
guard let post = post else { print("No post"); return }
print(post)
DispatchQueue.main.async {
cell.usernameLabel.text = post.username
cell.domainLabel.text = post.domain
cell.titleLabel.text = post.title
cell.detailsLabel.text = post.text
cell.postImage.sd_setImage(with: URL(string: "\(post.imageURL)"))
cell.timePassedLabel.text = post.createdHoursAgo
cell.ratingButton.setTitle("\(post.rating)", for: .normal)
cell.commentsButton.setTitle("\(post.comments)", for: .normal)
}
// pagination
}
return cell
}
Get one post:
class PostViewModel {
var limit = 30
let sub = "ios"
var portion = 5
var post: RedditPost?
func getPost(at postIndex: Int, completion: (#escaping (_ data: RedditPost?) -> Void)) {
UseCase().createPosts(sub: sub, limit: limit) { posts in
// there are not so many posts on reddit as you asked for (as limit)
if self.limit > posts.count {
print("Error: Number of demanded posts are bigger than available on Reddit")
completion(nil)
} else {
let post = posts[postIndex]
self.post = post
completion(post)
}
}
}
}
Get all posts from API:
class UseCase {
func createPosts(sub: String, limit: Int, completion: (#escaping (_ data: [RedditPost]) -> Void)) {
Repository().fillPostsArray(sub: sub, limit: limit) { (redditPosts: [RedditPost]) in
completion(redditPosts)
}
}
}
the first API call should be made from the viewDidLoad method and reload the tableView when the response comes.
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
loadPostsFromRedit()
}
private func loadPostsFromRedit() {
vm.getPost(at: indexPath.row) { [weak self] posts in
guard let self = self, let post = posts else {
return
}
print(posts)
self.post = posts
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Pagination:
You can use the willDisplayCell method to check if the user has reached the end of the tableView and call the API to load the next page
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if post.count == indexPath.row - 1 {
vm.loadMorePosts()
}
}
Note: you also need to pass the page index to redit API to return the correct page response. Refer this tutorial for more details https://www.raywenderlich.com/5786-uitableview-infinite-scrolling-tutorial
Related
I'm having some trouble passing my API returned data to table view cells. I am appending the data to an array and then passing this array to the table view (as usual) to get the number of rows and data for the cells. When I print inside the function where I am appending, the titles are shown in the array. Outside they're not. Any idea? Relevant code below:
class ProductTableViewController: UITableViewController, UISearchBarDelegate {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet var tabView: UITableView!
var filteredData = ["Title1"]
override func viewDidLoad() {
super.viewDidLoad()
getProducts { (products) in
for product in products {
self.filteredData.append(product.title)
}
}
}
func getProducts(completionHandler: #escaping([ProductDetail]) -> Void) {
let url = URL(string: "exampleAPIURL")!
let dataTask = URLSession.shared.dataTask(with: url) {data, _, _ in
guard let jsonData = data else { return }
do {
let decoder = JSONDecoder()
let productsResponse = try decoder.decode(Products.self, from: jsonData)
let productDetails = productsResponse.data
for name in productDetails {
self.filteredData.append(name.title)
}
completionHandler(productDetails)
}catch {
print(error.localizedDescription)
}
}
dataTask.resume()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if filteredData == nil {
return 1 }
else {
return filteredData.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as UITableViewCell
for name in filteredData {
if name != nil {
let product = filteredData[indexPath.row]
cell.textLabel?.text = product
} else {
cell.textLabel?.text = "name"
}
}
return cell
}
I am only receiving the hardcoded strings in the filteredData array when I run the simulator. Is there a different way to pass the JSON?
Many thanks!
Reload the table view after the data is collected:
getProducts { (products) in
for product in products {
self.filteredData.append(product.title)
}
self.tabView.reloadData()
}
After setting the array, you need to call self.tableView.reloadData() and invoke it on the main thread.
Also, its better to do the products API call from viewDidAppear as if the API call from viewDidLoad returns fast enough, operations on the view might fail. Also you might want to show some activity indicator.
override func viewDidAppear() {
super.viewDidLoad()
getProducts { (products) in
for product in products {
self.filteredData.append(product.title)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
im having trouble setting up pagination in swift with the MovieDB API
normally you would have a limit and an offet then that would relay to your model array .count -1
when working with CollectionViews
Im working with a diffable datasource and cannot see the solution
has anyone manage to implement this or something similar?
current API service looks like this
class APIService {
static let shared = APIService()
//always pass in your first API so the one which holds title, release date ect
func fetchMovies(completionHandler: #escaping ([Movie]?, Error?) -> ()) {
guard let url = URL(string: APINOWPLAYING) else {
print("not a valid url")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let data = data {//when Decoding use the 2nd API model with the array
if let decodedResponse = try? JSONDecoder().decode(Movies.self, from: data) {
DispatchQueue.main.async {
completionHandler(decodedResponse.results, nil)
print("TOTAL RESULTS \(decodedResponse.page)")
}
return
}
}
print("Fatch Failed \(error?.localizedDescription ?? "error unknown")")
}.resume()
}
view controller
private func setupDiffableDataSource() {
collectionView.dataSource = diffDataSource
//MARK:- SetupHeader under Compositional Sections Extension
setupHeader()
APIService.shared.fetchMovies { (movies, err) in
APIService.shared.fetchTopMovies { (movieGroup, err) in
var snapshot = self.diffDataSource.snapshot()
snapshot.appendSections([.topSection])
snapshot.appendItems(movies ?? [], toSection: .topSection)
snapshot.appendSections([.bottomSection])
let objects = movieGroup?.results ?? []
snapshot.appendItems(objects, toSection: .bottomSection)
self.diffDataSource.apply(snapshot)
}
}
}
does anyone know how to work with API for pagination?
this is what the MOVIEDB api call looks like
let APINOWPLAYING =
"https://api.themoviedb.org/3/movie/now_playing?api_key=(APIKEY)&language=en-US&page=1&total_pages=56"
hoping someone can point me in the right direction
thanks
You can use func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) from UICollectionViewDelegate
You need to update your service so it can handle the page parameter
var isCanLoadMore = false
var currentPage = 1
private func fetchData(page: Int) {
// your API request
// remember to change isCanLoadMore = true after apply(snapshot)
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if isCanLoadMore {
if diffableDataSource.snapshot().numberOfSections - 1 == indexPath.section {
let currentSection = diffableDataSource.snapshot().sectionIdentifiers[indexPath.section]
if diffableDataSource.snapshot().numberOfItems(inSection: currentSection) - 1 == indexPath.row {
isCanLoadMore = false
currentPage += 1
print("NEXT PAGE")
fetchData(page: currentPage)
}
}
}
}
I've got this JSON from an API:
{
"oldest": "2019-01-24T00:00:00+00:00",
"activities": [
{
"message": "<strong>Henrik</strong> didn't resist a guilty pleasure at <strong>Starbucks</strong>.",
"amount": 2.5,
"userId": 2,
"timestamp": "2019-05-23T00:00:00+00:00"
},
{
"message": "<strong>You</strong> made a manual transfer.",
"amount": 10,
"userId": 1,
"timestamp": "2019-01-24T00:00:00+00:00"
}
]
}
It has a lote more activities. How can I access it and fill my cells with its data? So far I've got this code:
MainViewController:
struct Activities: Decodable {
var oldest: String
var activities: [Activity]
}
struct Activity: Decodable {
var message: String
var amount: Float
var userId: Int
var timestamp: String
}
class MainTableViewController: UITableViewController, UITableViewDataSourcePrefetching {
var activityList: [Activities] = []
var activity: [Activity] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.prefetchDataSource = self
let activitiesJSONURLString = "https://qapital-ios-testtask.herokuapp.com/activities?from=2016-05-23T00:00:00+00:00&to=2019-05-23T00:00:00+00:00"
guard let activitiesURL = URL(string: activitiesJSONURLString) else { return }
URLSession.shared.dataTask(with: activitiesURL) { (data, response, err) in
// perhaps check err
// also perhaps check response status 200 OK
guard let data = data else { return }
do {
// Activities
let activities = try JSONDecoder().decode(Activities.self, from: data)
} catch let jsonErr {
print("Error serializing json: ", jsonErr)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}.resume()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return activityList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ActivityCell", for: indexPath) as! MainTableViewCell
return cell
}
// Prefetching
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
// if indexPaths.contains(where: isLoadingCell) {
// viewModel.fetchModerators()
// }
}
}
But I think something is off. Or I have no clue on how to start. I could really use some help or any tips you can give me. Please and thank you!
First of all the naming of the structs is pretty confusing. Name the root object with something unrelated like Response or Root.
And we are going to decode the timestamps as Date
struct Root: Decodable {
var oldest: Date
var activities: [Activity]
}
struct Activity: Decodable {
var message: String
var amount: Float
var userId: Int
var timestamp: Date
}
Second of all as the data is received in all the conformance to UITableViewDataSourcePrefetching is pointless. Remove it and delete also the prefetchRowsAt method.
Declare only one data source array and name it activities
var activities = [Activity]()
and delete
var activityList: Activities!
In the completion handler of the data task decode Root and assign the activities array to the data source array
do {
// Activities
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let result = try decoder.decode(Root.self, from: data)
self.activities = result.activities
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Error serializing json: ", error)
}
The table view data source methods are
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return activities.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ActivityCell", for: indexPath) as! MainTableViewCell
let activity = activities[indexPath.row]
// assign the activity data to the UI for example
// cell.someLabel = activity.amount
return cell
}
Because you are using the activityList to determine the number of rows, I'm assuming that you want to use the data from activityList in order to populate your ActivityCells. That is, unless, you meant for activityList to be a single instance of Activities instead of an array of Activities, in which case you would likely use activityList.activities.count in order to determine the number of rows. In either case, lets just call the array of data you want to use to fill the cells activityList.
In this case, you should make sure to update activityList to the activities that you have fetched from the API. Once you have the activityList, you can then use reloadData which will trigger your table view delegate methods. In tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) you can then use activityList in order to update the dequeued cell.
Something like this might be what you want:
class MainTableViewController: UITableViewController, UITableViewDataSourcePrefetching {
var activityList: Activities!
var activity: [Activity] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.prefetchDataSource = self
let activitiesJSONURLString = "https://qapital-ios-testtask.herokuapp.com/activities?from=2016-05-23T00:00:00+00:00&to=2019-05-23T00:00:00+00:00"
guard let activitiesURL = URL(string: activitiesJSONURLString) else { return }
URLSession.shared.dataTask(with: activitiesURL) { (data, response, err) in
// perhaps check err
// also perhaps check response status 200 OK
guard let data = data else { return }
do {
// Activities
let activities = try JSONDecoder().decode(Activities.self, from: data)
self.activityList = activities
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let jsonErr {
print("Error serializing json: ", jsonErr)
}
}.resume()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return activityList.activities.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ActivityCell", for: indexPath) as! MainTableViewCell
if let activity = activityList?[indexPath.row] {
// UPDATE CELL ACCORDING TO activity
}
return cell
}
}
So I have a tableview controller which has headers and cells which are supposed to be present under those headers. I have already done the job of correctly creating the headers. I currently have prepared the datasource for the cells which takes its content from the a list of currentEvents that a user is currently attending. The issue isn't the pulling of the data it seems to be the completion block. It takes so long to come back that the currentEvents array never gets appended when it is supposed to. It also keeps appending events onto the original array even after it should reset upon entering the new section. I have tried many things and moved code around many places but nothing seems to be having any effect
The key function in all this is
self.fetchEventsFromServer()
specifically the part where the EventService.show takes place
import UIKit
import Firebase
class FriendsEventsView: UITableViewController{
var cellID = "cellID"
var friends = [Friend]()
var attendingEvents = [Event]()
//label that will be displayed if there are no events
var currentUserName: String?
var currentUserPic: String?
var currentEventKey: String?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Friends Events"
view.backgroundColor = .white
// Auto resizing the height of the cell
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "close_black").withRenderingMode(.alwaysOriginal), style: .done, target: self, action: #selector(self.goBack))
tableView.register(EventDetailsCell.self, forCellReuseIdentifier: cellID)
self.tableView.tableFooterView = UIView(frame: CGRect.zero)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global(qos: .background).async {
print("This is run on the background queue")
self.fetchEventsFromServer { (error) in
if error != nil {
print(error)
return
} else {
DispatchQueue.main.async {
self.tableView.reloadData()
print("This is run on the main queue, after the previous code in outer block")
}
}
}
}
}
#objc func goBack(){
dismiss(animated: true)
}
override func numberOfSections(in tableView: UITableView) -> Int {
// print(friends.count)
return friends.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// print(friends[section].events.count)
return friends[section].collapsed ? 0 : friends[section].events.count
}
func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! EventDetailsCell? ?? EventDetailsCell(style: .default, reuseIdentifier: cellID)
// print(indexPath.row)
cell.details = friends[indexPath.section].events[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as? CollapsibleTableViewHeader ?? CollapsibleTableViewHeader(reuseIdentifier: "header")
// print(section)
header.arrowLabel.text = ">"
header.setCollapsed(friends[section].collapsed)
print(friends[section].collapsed)
header.section = section
// header.delegate = self
header.friendDetails = friends[section]
return header
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func fetchEventsFromServer(_ completion: #escaping (_ error: Error?) -> Void ){
//will grab the uid of the current user
guard let myUserId = Auth.auth().currentUser?.uid else {
return
}
let ref = Database.database().reference()
//checking database for users that the current user is following
ref.child("following").child(myUserId).observeSingleEvent(of: .value, with: { (followingSnapshot) in
//handling potentail nil or error cases
guard let following = followingSnapshot.children.allObjects as? [DataSnapshot]
else {return}
//validating if proper data was pulled
let group = DispatchGroup()
for followingId in following {
group.enter()
print(followingId.key)
ref.child("users").child(followingId.key).observeSingleEvent(of: .value, with: { (userInfoSnapShot) in
guard let followingUserInfo = userInfoSnapShot.children.allObjects as? [DataSnapshot] else {
return
}
//validating if proper data was pulled for each follower
for currentUserInfo in followingUserInfo {
//will add this back when I want to event implementation
if currentUserInfo.key == "Attending" {
guard let eventKeys = currentUserInfo.children.allObjects as? [DataSnapshot] else{return}
for event in eventKeys {
print(event.key)
EventService.show(forEventKey: event.key, completion: { (event) in
guard let currentEvent = event else{
return
}
self.attendingEvents.append(currentEvent)
print(self.attendingEvents.count)
})
}
}
if currentUserInfo.key == "profilePic"{
self.currentUserPic = currentUserInfo.value as! String
//print(self.currentUserPic)
}
if currentUserInfo.key == "username"{
self.currentUserName = currentUserInfo.value as! String
//print(self.currentUserName)
var friend = Friend(friendName: self.currentUserName!, events: self.attendingEvents, imageUrl: self.currentUserPic!)
print(friend.events.count)
self.friends.append(friend)
//print(self.friends.count)
}
}
group.leave()
let result = group.wait(timeout: .now() + 0.01)
completion(nil)
}, withCancel: { (err) in
completion(err)
print("Couldn't grab info for the current list of users: \(err)")
})
}
completion(nil)
}) { (err) in
completion(err)
print("Couldn't grab people that you are currently following: \(err)")
}
}
}
If anyone sees something I don't please dont hesitate to say something
How would I alter my dispatch group implementation to accomplish this goal
EventService Function
struct EventService {
static func show(forEventKey eventKey: String, completion: #escaping (Event?) -> Void) {
// print(eventKey)
let ref = Database.database().reference().child("events").child(eventKey)
// print(eventKey)
//pull everything
ref.observeSingleEvent(of: .value, andPreviousSiblingKeyWith: { (snapshot,eventKey) in
// print(snapshot.value ?? "")
guard let event = Event(snapshot: snapshot) else {
return completion(nil)
}
completion(event)
})
}
}
people, I have this issue when I try back image from different cell
(Thread 1: Fatal error: Index out of range)
what I'm doing here ?
I'm trying to build an Instagram clone and in my home view controller that what should posts show up. I make navigation with a table view and that table view has 2 cell with the different identifier. cell number 1 it's a header that brings data from users table to my username label and profile image. and cell number 2 its for posts its should bring post data like image and caption. I use firebase database.
my code :
import UIKit
import FirebaseAuth
import FirebaseDatabase
class HomeViewController: UIViewController ,UITableViewDelegate {
#IBOutlet weak var tableview: UITableView!
var posts = [Post]()
var users = [UserD]()
override func viewDidLoad() {
super.viewDidLoad()
tableview.dataSource = self
loadposts()
userDetal()
// var post = Post(captiontxt: "test", photoUrlString: "urll")
// print(post.caption)
// print(post.photoUrl)
}
func loadposts() {
Database.database().reference().child("posts").observe(.childAdded){ (snapshot: DataSnapshot)in
print(Thread.isMainThread)
if let dict = snapshot.value as? [String: Any]{
let captiontxt = dict["caption"] as! String
let photoUrlString = dict["photoUrl"] as! String
let post = Post(captiontxt: captiontxt, photoUrlString: photoUrlString)
self.posts.append(post)
print(self.posts)
self.tableview.reloadData()
}
}
}
func userDetal() {
Database.database().reference().child("users").observe(.childAdded){ (snapshot: DataSnapshot)in
print(Thread.isMainThread)
if let dict = snapshot.value as? [String: Any]{
let usernametxt = dict["username"] as! String
let profileImageUrlString = dict["profileImageUrl"] as! String
let user = UserD(usernametxt: usernametxt, profileImageUrlString: profileImageUrlString)
self.users.append(user)
print(self.users)
self.tableview.reloadData()
}
}
}
#IBAction func logout(_ sender: Any) {
do {
try Auth.auth().signOut()
}catch let logoutErrorr{
print(logoutErrorr)
}
let storyboard = UIStoryboard(name: "Start", bundle: nil)
let signinVC = storyboard.instantiateViewController(withIdentifier: "SigninViewController")
self.present(signinVC, animated: true, completion: nil)
}
}
extension HomeViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell = tableview.dequeueReusableCell(withIdentifier: "imagecell", for: indexPath) as! PostCellTableViewCell
cell.postimage.image = nil
cell.tag += 1
let tag = cell.tag
cell.captionLabel.text = posts[indexPath.row].caption
let photoUrl = posts[indexPath.row].photoUrl
getImage(url: photoUrl) { photo in
if photo != nil {
if cell.tag == tag {
DispatchQueue.main.async {
cell.postimage.image = photo
}
}
}
}
return cell
} else if indexPath.row == 1 {
let cell = tableview.dequeueReusableCell(withIdentifier: "postcell", for: indexPath) as! HeaderTableViewCell
cell.userimage.image = nil
cell.tag += 1
let tag = cell.tag
cell.usernamelabel.text = users[indexPath.row].username
//Error showing here????????????????????????????????????
let profileImageUrl = users[indexPath.row].profileImageUrl
getImage(url: profileImageUrl) { photo in
if photo != nil {
if cell.tag == tag {
DispatchQueue.main.async {
cell.userimage.image = photo
}
}
}
}
return cell
}
return UITableViewCell()
}
func getImage(url: String, completion: #escaping (UIImage?) -> ()) {
URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
if error == nil {
completion(UIImage(data: data!))
} else {
completion(nil)
}
}.resume()
}
}
try this one.
cell.tag = indexpath.row
What is the content of users array ?
Are you sure you want to define as many sections as users or as many rows ?
In this case use
func numberOfRows(in tableView: NSTableView) -> Int {
return users.count
}
As explained, you need to rewrite completely cellForRowAt
It should look like this :
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if row < users.count {
let user = users[row]
if let cellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CellID"), owner: self) {
(cellView as! NSTableCellView).textField?.stringValue = user.name
// do the same for all the fields you need to set
return cellView
} else {
return nil
}
}
return nil
}
thanx, my friend, I found a good way to contain my cell. for post cell, i just use cellForRowAt and but the post data. for header cell i use viewForHeaderInSection
and but my user data with heightForHeaderInSection. to make the high for a view