swift table section header duplicating on scroll - ios

I am admitting defeat with these custom headers in Swift. I have tried for days to prevent the labels and images inside of the section headers from duplicating. Basically on scroll the labels/images inside the headers duplicate and lay on top of each other.
Would someone please for the love of god explain to me why this is happening and how to fix it.
The circular image keeps creating images and laying them on top of the previous, the same with the name and date labels!!
Here is my ViewController:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var navigationBar: UINavigationItem!
#IBOutlet weak var tableView: UITableView!
var list = []
var audioPlayer:AVAudioPlayer!
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func getListBro() -> NSArray {
return list
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.dataSource = self;
tableView.delegate = self;
self.tableView.rowHeight = UITableViewAutomaticDimension
let streamURL = NSURL(string: "http://192.241.174.8:8000/beat-stream-all/")!
let stuff = GetBeatStream(url:streamURL)
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 50.0; //
stuff.downloadJSONFromURL {
(let jsonDictionary) in
if let jsonList = jsonDictionary["results"] as? NSArray {
self.list = jsonList
}
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
// do some task
dispatch_async(dispatch_get_main_queue()) {
// update some UI
self.tableView.reloadData()
}
}
}
}//end view did load
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("streamCell", forIndexPath: indexPath) as! StreamTableViewCell
cell.beatCover.image = UIImage(named: "imgres")
if let id = list[indexPath.section] as? NSDictionary {
if let beatID = id["id"] as? NSInteger {
cell.setID(beatID)
}
}
if let beat_cover = list[indexPath.section] as? NSDictionary {
if let beat_cover_image = beat_cover["beat_cover"] as? String {
cell.beatCover.downloadImageFrom(link: beat_cover_image, contentMode: UIViewContentMode.ScaleToFill) //set your image from link array.
}
}
if let audio = list[indexPath.section] as? NSDictionary {
if let audio_url = audio["audio"] as? String {
cell.url = audio_url
cell.buildPlayer()
}
}
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return list.count
}
func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let beatAuthorLabel = UILabel(frame: CGRectMake(55, 5, 200, 40))
//USER PIC
let profilePictureImageView = UIImageView(frame: CGRectMake(5, 5, 40, 40));
profilePictureImageView.layer.borderWidth = 0
profilePictureImageView.layer.masksToBounds = false
profilePictureImageView.layer.borderColor = UIColor.blackColor().CGColor
profilePictureImageView.layer.cornerRadius = profilePictureImageView.frame.height/2
profilePictureImageView.clipsToBounds = true
profilePictureImageView.layer.masksToBounds = true
profilePictureImageView.image = UIImage(named: "imgres") //set placeholder image first.
if let userPicSection = list[section] as? NSDictionary {
if let artist = userPicSection["artist"] as? NSDictionary {
if let profilePic = artist["profile_pic"] as? String {
profilePictureImageView.downloadImageFrom(link: profilePic, contentMode: UIViewContentMode.ScaleAspectFit)
}
}
}
if let nameSection = list[section] as? NSDictionary {
if let name = nameSection["artist"] as? NSDictionary {
if let adminName = name["admin_name"] as? NSString {
print(adminName)
beatAuthorLabel.text = adminName as String
beatAuthorLabel.font = UIFont(name: beatAuthorLabel.font.fontName, size: 14)
}
}
}
var dateLabel = UILabel(frame: CGRectMake(225, 5, 200, 40))
if let created = list[section] as? NSDictionary {
if let date = created["created_at"] as? String {
dateLabel.text = date as String
dateLabel.font = UIFont(name: dateLabel.font.fontName, size: 8)
}
}
let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
header.contentView.addSubview(beatAuthorLabel)
header.contentView.addSubview(dateLabel)
header.contentView.addSubview(dateLabel)
header.contentView.addSubview(profilePictureImageView)
header.contentView.backgroundColor = UIColor(red: 179/255, green: 194/255, blue: 191/255, alpha:1)
header.textLabel!.textColor = UIColor.whiteColor()
header.alpha = 1
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = tableView.indexPathForSelectedRow
let currentCell = tableView.cellForRowAtIndexPath(indexPath!)! as! StreamTableViewCell
currentCell.player.play()
}
func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let leavingCell = cell as! StreamTableViewCell
leavingCell.player.pause()
}
}

Header views, like cells, are reused. So, when the table view sends you tableView(_:willDisplayHeaderView:), it might not be the first time you've received that message for that header view. Yet every time you receive the message, you add four subviews to it.
Don't implement tableView(_:willDisplayHeaderView:forSection:) at all.
Instead, make a subclass of UITableViewHeaderFooterView with properties for the different subviews. This is just like how you created a subclass of UITableViewCell named StreamTableViewCell. Maybe you could call your header subview class StreamSectionHeaderView. Then you have two options for setting up the header view's subviews.
Option 1: In StreamSectionHeaderView.initWithFrame(_:), create the subviews of the header view (and store them in the instance properties and add them as subviews). This is essentially what you're doing now in tableView(_:willDisplayHeaderView:forSection:), but you would move most of the code into the StreamSectionHeaderView class. Register the StreamSectionHeaderView class with the table view using UITableView.registerClass(_:forHeaderFooterViewReuseIdentifier:).
Option 2: Design the header view and its subviews in a XIB (you can't do it in a storyboard), connect the subviews to the StreamSectionHeaderView properties (which must be IBOutlets in this case), and register the XIB in the table view with UITableView.registerNib(_:forHeaderFooterViewReuseIdentifier:).
To produce section, implement tableView(_:viewForHeaderInSection:) by calling tableView.
dequeueReusableHeaderFooterViewWithIdentifier(_:) and then configuring the header view's subviews, which already exist by the time dequeueReusableHeaderFooterViewWithIdentifier(_:) returns.
UPDATE
Here's your StreamSectionHeaderView, assuming you want to set up its subviews in code:
class StreamSectionHeaderView: UITableViewHeaderFooterView {
// Make these IBOutlets if you design StreamSectionHeaderView in a XIB.
var beatAuthorLabel = UILabel(frame: CGRectMake(55, 5, 200, 40))
var profilePictureImageView = UIImageView(frame: CGRectMake(5, 5, 40, 40))
var dateLabel = UILabel(frame: CGRectMake(225, 5, 200, 40))
init(frame: CGRect) {
profilePictureImageView.layer.borderWidth = 0
profilePictureImageView.layer.masksToBounds = false
profilePictureImageView.layer.borderColor = UIColor.blackColor().CGColor
profilePictureImageView.layer.cornerRadius = profilePictureImageView.frame.height/2
profilePictureImageView.clipsToBounds = true
profilePictureImageView.layer.masksToBounds = true
profilePictureImageView.image = UIImage(named: "imgres")
beatAuthorLabel.font = UIFont(name: beatAuthorLabel.font.fontName, size: 14)
dateLabel.font = UIFont(name: dateLabel.font.fontName, size: 8)
contentView.addSubview(beatAuthorLabel)
contentView.addSubview(dateLabel)
contentView.addSubview(profilePictureImageView)
contentView.backgroundColor = UIColor(red: 179/255, green: 194/255, blue: 191/255, alpha:1)
textLabel!.textColor = UIColor.whiteColor()
alpha = 1
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Then, in your table view controller:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
// blah blah blah other stuff
tableView.registerClass(StreamSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: "Header")
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterViewWithIdentifier("Header") as! StreamSectionHeaderView
if let userPicSection = list[section] as? NSDictionary {
if let artist = userPicSection["artist"] as? NSDictionary {
if let profilePic = artist["profile_pic"] as? String {
header.profilePictureImageView.downloadImageFrom(link: profilePic, contentMode: UIViewContentMode.ScaleAspectFit)
}
}
}
if let nameSection = list[section] as? NSDictionary {
if let name = nameSection["artist"] as? NSDictionary {
if let adminName = name["admin_name"] as? NSString {
print(adminName)
header.beatAuthorLabel.text = adminName as String
}
}
}
if let created = list[section] as? NSDictionary {
if let date = created["created_at"] as? String {
header.dateLabel.text = date as String
}
}
return header
}
}

Related

why Xcode do not execute the codes in func tableView?

question:
I set several breakpoints inside the function of tableView. However the Xcode didn't execute the code inside the tableView. please tell me how to fix these.
I'm new to learn about IOS development and I'm trying to write a demo of Tweeter show page. Looking forward for responses!
Here is code of extension UITablewViewDataSource:
extension WechatMomentViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.tweetList?.count ?? 0;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let tweetCell = tableView.dequeueReusableCell(withIdentifier: "WechatMomentListCell", for: indexPath) as? WechatMomentListCell else {
fatalError("there is no WechatMomentList")
}
let tweet = viewModel.tweetList?[indexPath.row]
for i in tweet?.images ?? [] {
let flagImage = UIImageView()
flagImage.sd_setImage(with: URL(string: i.url))
tweetCell.Images.append(flagImage)
}
for i in tweet?.comments ?? [] {
let flagComment = UILabel()
flagComment.text = "\(i.sender) : \(i.content)"
tweetCell.comments.append(flagComment)
}
tweetCell.senderNick.text = tweet?.sender?.nick
tweetCell.senderAvatar.sd_setImage(with: URL(string: tweet?.sender?.avatar ?? ""), placeholderImage: UIImage(named: "placeholder.png"))
tweetCell.content.text = tweet?.content
return tweetCell
}
}
and here is the code of all ViewController:
import UIKit
import SnapKit
import SDWebImage
import Alamofire
//
class WechatMomentViewController: UIViewController {
let viewModel = WechatMomentViewModel()
var userAvatar = UIImageView()
var userProfileImage = UIImageView()
var userNick = UIButton()
var TweetCell = UITableViewCell()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.delegate = self
getUserProfile()
getTweet()
}
fileprivate func getUserProfile() {
viewModel.getUserProfile()
view.addSubview(userProfileImage)
userAvatar.backgroundColor = UIColor.black
view.addSubview(userAvatar)
userAvatar.snp.makeConstraints{ (make) in
make.height.equalTo(80)
make.width.equalTo(80)
make.right.equalToSuperview().offset(-10)
make.centerY.equalToSuperview()
}
userAvatar.clipsToBounds = true;
userAvatar.layer.cornerRadius = 10;
view.addSubview(userNick)
userNick.snp.makeConstraints{ (make) in
make.width.equalTo(90)
make.height.equalTo(20)
make.trailing.equalTo(userAvatar.snp.leading)
make.centerY.equalTo(userAvatar)
}
userProfileImage.frame = CGRect(x: 0, y: 0, width: 414, height: 448)
}
fileprivate func getTweet() {
viewModel.getTweet()
}
}
extension WechatMomentViewController: WechatMomentVCProtocol {
func refreshUI() {
if let user = viewModel.user,
let avatar = user.avatar,
let profileImage = user.profileImage {
userAvatar.sd_setImage(with: URL(string: avatar))
userProfileImage.sd_setImage(with: URL(string: profileImage))
userNick.setTitle(user.nick, for: .normal)
}
}
}
extension WechatMomentViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModel.tweetList?.count ?? 0;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let tweetCell = tableView.dequeueReusableCell(withIdentifier: "WechatMomentListCell", for: indexPath) as? WechatMomentListCell else {
fatalError("there is no WechatMomentList")
}
let tweet = viewModel.tweetList?[indexPath.row]
for i in tweet?.images ?? [] {
let flagImage = UIImageView()
flagImage.sd_setImage(with: URL(string: i.url))
tweetCell.Images.append(flagImage)
}
for i in tweet?.comments ?? [] {
let flagComment = UILabel()
flagComment.text = "\(i.sender) : \(i.content)"
tweetCell.comments.append(flagComment)
}
tweetCell.senderNick.text = tweet?.sender?.nick
tweetCell.senderAvatar.sd_setImage(with: URL(string: tweet?.sender?.avatar ?? ""), placeholderImage: UIImage(named: "placeholder.png"))
tweetCell.content.text = tweet?.content
return tweetCell
}
}
and here is identifire code of WechatMomentListCell:
import Foundation
import UIKit
class WechatMomentListCell: UITableViewCell{
var content = UILabel()
var senderAvatar = UIImageView()
var senderNick = UILabel()
var Images = [UIImageView()]
var comments = [UILabel()]
}
your class should extend UITableViewController not UIViewController . Also you must register you cell.
class WechatMomentViewController: UITableViewController {
let kCellIdentifier = "CellIdentifier"
override func viewDidLoad() {
super.viewDidLoad()
self.tableView?.register(WechatMomentListCell, forCellReuseIdentifier: kCellIdentifier)
}
}
Add something like:
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self // if you are...
tableView.datasource = self
...
}
The problem ist that you will most likely (you didn't show) have a regular UIViewController and not a UITableViewController. Therefore the tableview does not have a delegate nor a datasource. Alternatively, if you are using a storyboard or a xib, you can right click from the tableview to the controller and assign those values there.
The problem is:
firstly, I don't have TableView(). So I create a new instance of TableView.
Secondly, use
view.addSubivew(tableview)
Then, member to reloadData from your View Model.
and... try clean build(it is really helpful)

Reusable UITableViewCell duplicating on scroll in Swift

Revisiting some old code I once did to start learning how code works when developing iOS applications so I apologize for the godlike viewController. The problem when running the app is that once a user begins to scroll the tableview, the labels are beginning to duplicate on top of each other and I'm pretty sure the webviews are most likely stacking as well. I've tried changing the code where I add the subviews in the cell by using the cell.contentview.addsubview() method but that doesn't change the stacking behavior when scrolling. I'm using the dequeueReusableCellWithIdentifier() method as well which I thought clears out cells all together once it goes off screen.
Any help here would be greatly appreciated as I'm still learning. Thank you in advance.
import UIKit
class VimeoFeedViewController: UIViewController, NSXMLParserDelegate, UITableViewDataSource, UIWebViewDelegate, UITableViewDelegate
{
#IBOutlet var tbData: UITableView?
var parser = NSXMLParser()
var posts = NSMutableArray()
var elements = NSMutableDictionary()
var element = NSString()
var title1 = NSMutableString()
var date = NSMutableString()
var link = NSMutableString()
var webView = UIWebView()
var boxView = UIView()
var selectedCell = NSIndexPath()
var valueToPass:String!
var viewToPass: UIView!
var customWebView = UIWebView()
var url = NSURL()
//custom code for webviews to show up
var postTitle: String = String()
var postLink: String = String()
var ename: String = String()
//end of custom code for webviews to show up
override func viewDidLoad() {
super.viewDidLoad()
self.beginParsing()
self.tbData?.backgroundColor = UIColor(patternImage: UIImage(named: "home-page-background.png")!)
webView.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func beginParsing()
{
posts = []
parser = NSXMLParser(contentsOfURL:(NSURL(string:"https://vimeo.com/channels/1000464/videos/rss"))!)!
parser.delegate = self
parser.parse()
tbData!.reloadData()
}
//XMLParser Methods
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String])
{
element = elementName
if (elementName as NSString).isEqualToString("item")
{
elements = NSMutableDictionary()
elements = [:]
title1 = NSMutableString()
title1 = ""
date = NSMutableString()
date = ""
link = NSMutableString()
link = ""
postTitle = String()
postLink = String()
}
}
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?)
{
if (elementName as NSString).isEqualToString("item") {
if !title1.isEqual(nil) {
elements.setObject(title1, forKey: "title")
}
if !date.isEqual(nil) {
elements.setObject(date, forKey: "date")
}
if !link.isEqual(nil) {
elements.setObject(link, forKey: "link")
}
posts.addObject(elements)
}
}
func parser(parser: NSXMLParser, foundCharacters string: String)
{
if element.isEqualToString("title") {
title1.appendString(string)
}
if element.isEqualToString("pubDate") {
date.appendString(string)
}
if element.isEqualToString("link") {
link.appendString(string)
}
}
//Tableview Methods
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 100.0
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 40))
let imageName = "broughtToYouAG.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = CGRect(x: 0, y: 0, width: 420, height: 91)
headerView.addSubview(imageView)
headerView.backgroundColor = UIColor(patternImage: UIImage(named: "home-page-background.png")!)
return headerView
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return posts.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
var cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell")!
if(cell.isEqual(NSNull)) {
cell = NSBundle.mainBundle().loadNibNamed("Cell", owner: self, options: nil)![0] as! UITableViewCell;
}
//start of customization
let textLabelCustom = UILabel(frame: CGRectMake(20, 0, 200, 91))
let detailTextLabelCustom = UILabel(frame: CGRectMake(20, 0, 200, 20))
let customWebView:UIWebView = UIWebView(frame: CGRectMake(245, 5, 149, 80))
let url: NSURL? = NSURL(string: posts.objectAtIndex(indexPath.row).valueForKey("link") as! NSString as String)
let request: NSURLRequest = NSURLRequest(URL: url!)
customWebView.loadRequest(request)
customWebView.delegate = self
customWebView.scalesPageToFit = true
cell.contentView.addSubview(customWebView)
cell.contentView.addSubview(boxView)
//cell.addSubview(customWebView)
//cell.addSubview(boxView)
textLabelCustom.text = posts.objectAtIndex(indexPath.row).valueForKey("title") as! NSString as String
textLabelCustom.numberOfLines = 4
detailTextLabelCustom.numberOfLines = 2
textLabelCustom.textColor = UIColor.blackColor()
detailTextLabelCustom.font = UIFont(name: "AmericanTypewriter", size: 15)
textLabelCustom.font = UIFont(name: "HelveticaNeue", size: 18)
//cell.addSubview(textLabelCustom)
cell.contentView.addSubview(textLabelCustom)
//cell.addSubview(detailTextLabelCustom)
cell.contentView.addSubview(detailTextLabelCustom)
cell.backgroundColor = UIColor(patternImage: UIImage(named: "home-page-background.png")!)
let backgroundView = UIView()
backgroundView.backgroundColor = UIColor(patternImage: UIImage(named: "tableselectedimage.png")!)
cell.selectedBackgroundView = backgroundView
return cell as UITableViewCell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You selected cell #\(indexPath.row)!")
let indexPath = tbData!.indexPathForSelectedRow;
let url: NSURL? = NSURL(string: posts.objectAtIndex(indexPath!.row).valueForKey("link") as! NSString as String)
print("Video url selected: \(url)!")
}
override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!) {
if (segue.identifier == "vimeoWebSegue") {
//get a reference to the destination view controller
let destinationVC: playingThatVideoViewController = segue.destinationViewController as! playingThatVideoViewController
let indexPath = tbData!.indexPathForSelectedRow;
let url: NSURL? = NSURL(string: posts.objectAtIndex(indexPath!.row).valueForKey("link") as! NSString as String)
let titlegoing = posts.objectAtIndex(indexPath!.row).valueForKey("title") as! NSString as String
//set properties on the destination view controller
destinationVC.videoWebView = customWebView
destinationVC.urlbrought = url!
destinationVC.titlebrought = titlegoing
}
}
}
You should create a custom UITableViewCell with desirable views (in your case 2 UILabels and UIWebView), load this cell from nib in cellForRowAtIndexPath and set needed values from arrays at indexPath.row appropriately. Don't create and add views in cellForRowAtIndexPath, just set values.
I'm using the dequeueReusableCellWithIdentifier() method as well which
I thought clears out cells all together once it goes off screen
It does not clear the cells.
It reuses cells that go off screen to display the cells that come on screen.
You should be removing the views before adding them again.
One way to accomplish this would be setting a tag for the view (view.tag = 10), and when dequeing a cell you cand do cell.contentView.viewWithTag(10)?.removeFromSuperview().
As the other answers suggest that you remove and re-add the views. Id rather suggest you add them once, and reuse the views. Subclass your cell and add the subviews once. Then use prepareForReuse to clear the text label.text = nil, for example.
For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state.
There are Two Possible Reason of this bug.
Reason 1
in your cellForRowAtIndexPath method
you are creating two labels on the same position which is overlapping one on other.
//Here is your code
//Check the x-axis and y-axis of both textLabelCustom and detailTextLabelCustom are same.
//It means both label will place on same axis but with different heights.
let textLabelCustom = UILabel(frame: CGRectMake(20, 0, 200, 91))
let detailTextLabelCustom = UILabel(frame: CGRectMake(20, 0, 200, 20))
let customWebView:UIWebView = UIWebView(frame: CGRectMake(245, 5, 149, 80))
Change it like this. And increase you cell size as well.
let textLabelCustom = UILabel(frame: CGRectMake(20, 0, 200, 90))
let detailTextLabelCustom = UILabel(frame: CGRectMake(20, 90, 200, 20))
let customWebView:UIWebView = UIWebView(frame: CGRectMake(245, 5, 149, 80))
Reason 2
Implement another delegate method didEndDisplayingCell and write below line
cell.contentView.removeAllSubviews()
In cellForRowAtIndexPath, since the cells for reused (hence the method name 'reusableCell'), you'll have to remove all the cell's subviews before you add more subviews to the cell.
for subview in cell.contentView.subviews {
subview.contentView.removeFromSuperview()
}

UIView inside UIView with TextField and Button not working

Good afternoon,
I'm trying to show a UIView when (in my case) there isn't any result to show in a tableView filled with products. When I detect 0 products, I show a UIView which contains a Label, a TextField and a Button, but I can't interact with my TextField and neither with the Button.
It's my first time using this technique to show a UIView when something went wrong with the tableView so I would like to know what's wrong in my code and what I'm missing because it's really weird.
Here is my code (when I print "Product not found" is where I show the UIView):
import UIKit
import Social
class ProductoCamViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var productoImageView:UIImageView!
#IBOutlet var tableView:UITableView!
#IBOutlet weak var noEncontrado:UIView!
var productoImage:String!
var ean:String!
var producto:Producto!
var productos = [Producto]()
#IBOutlet weak var toolBar: UIToolbar!
#IBOutlet weak var cargando: UIActivityIndicatorView!
override func viewDidLoad() {
toolBar.hidden = true
noEncontrado.hidden = true
cargando.hidden = false
super.viewDidLoad()
// Set table view background color
self.tableView.backgroundColor = UIColor(red: 240.0/255.0, green: 240.0/255.0, blue: 240.0/255.0, alpha: 0.2)
// Remove extra separator
self.tableView.tableFooterView = UIView(frame: CGRectZero)
// Change separator color
self.tableView.separatorColor = UIColor(red: 240.0/255.0, green: 240.0/255.0, blue: 240.0/255.0, alpha: 0.8)
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 88.0
requestPost()
cargando.hidden = true
tableView.reloadData()
}
override func viewDidAppear(animated: Bool) {
tableView.reloadData()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.hidesBarsOnSwipe = false
self.navigationController?.setNavigationBarHidden(false, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func requestPost () {
let request = NSMutableURLRequest(URL: NSURL(string: "http://www.mywebsite.com/product.php")!)
request.HTTPMethod = "POST"
let postString = "ean="+ean
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print("error=\(error)")
return
}
// JSON RESULTADO ENTERO
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)!
if (responseString == "Product not found")
{
self.noEncontrado.hidden = false
self.tableView.reloadData()
return
}
else
{
self.productos = self.parseJsonData(data!)
self.toolBar.hidden = false
// Reload table view
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
}
task.resume()
}
func parseJsonData(data: NSData) -> [Producto] {
var productos = [Producto]()
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
noEncontrado.hidden = true
// Parse JSON data
let jsonProductos = jsonResult?["lista_productos"] as! [AnyObject]
for jsonProducto in jsonProductos {
let producto = Producto()
producto.imagen = jsonProducto["imagen"] as! String
producto.nombre = jsonProducto["nombre"] as! String
producto.descripcion = jsonProducto["descripcion"] as! String
producto.modo_de_empleo = jsonProducto["modo_de_empleo"] as! String
producto.marca = jsonProducto["marca"] as! String
producto.linea = jsonProducto["linea"] as! String
producto.distribuidor = jsonProducto["distribuidor"] as! String
producto.tamano = jsonProducto["tamano"] as! String
producto.precio = jsonProducto["precio"] as! String
producto.codigo_nacional = jsonProducto["codigo_nacional"] as! String
productos.append(producto)
}
}
catch let parseError {
print(parseError)
}
return productos
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// Return the number of sections.
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return productos.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
title = productos[indexPath.row].nombre
let cell = tableView.dequeueReusableCellWithIdentifier("CellDetail", forIndexPath: indexPath) as! ProductoTableViewCell
cell.selectionStyle = .None
if let url = NSURL(string: productos[indexPath.row].imagen) {
if let data = NSData(contentsOfURL: url) {
self.productoImageView.image = UIImage(data: data)
}
}
cell.nombre.text = productos[indexPath.row].nombre
cell.descripcion.text = productos[indexPath.row].descripcion
cell.modo_de_empleo.text = productos[indexPath.row].modo_de_empleo
cell.marca.text = productos[indexPath.row].marca
cell.linea.text = productos[indexPath.row].linea
cell.distribuidor.text = productos[indexPath.row].distribuidor
cell.tamano.text = productos[indexPath.row].tamano
cell.precio.text = productos[indexPath.row].precio
cell.codigo_nacional.text = productos[indexPath.row].codigo_nacional
cell.layoutIfNeeded()
return cell
}
}
Thanks in advance.
At first, please try to provide english code :) but anyways. I think the view what should appear is nonEncontrado.
There could be some issues but i need to see the storyboard.
The view has userInteraction not enabled. Its a property and can also be activated in the storyboard
The view is overlayed by something else. Maybe the empty tableView.
As an suggestion you could provide this fields in the tableView and just load another DataSource. Than you dont need to fight with extra views. If you provide screens from the Storyboard i could help a bit more.
Good Luck :)

Empty Horizontal Table Cells

I've been following (and converting to swift) a Ray Wenderlich tutorial (link to tutorial) on how to make an interface with horizontal tables. My application loads but does not show any data in the cells as below:
I have three swift files in the project:ArticleTableViewCell.swiftHorizontalTableViewCell.swiftArticleListViewController.swift
// ArticleTableViewCell.swift
// HorizontalTables
import UIKit
class ArticleTableViewCell: UITableViewCell {
var thumbnail: UIImageView!
var titleLabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
var frameA: CGRect = CGRectMake(3, 3, 100, 314)
thumbnail = UIImageView(frame: frameA)
thumbnail.opaque = true
self.contentView.addSubview(thumbnail)
var frameB: CGRect = CGRectMake(0, thumbnail.frame.size.height * 0.632, thumbnail.frame.size.width, thumbnail.frame.size.height * 0.37)
titleLabel = UILabel(frame: frameB)
titleLabel.opaque = true
titleLabel.backgroundColor = UIColor.clearColor()
titleLabel.textColor = UIColor.blackColor()
titleLabel.numberOfLines = 2
titleLabel.font = UIFont(name: "Helvetica Neue", size: 12)
thumbnail.addSubview(titleLabel)
self.transform = CGAffineTransformMakeRotation(CGFloat(M_PI) * 0.5)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func reuseIdentifier() -> String {
return "ArticleCell"
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
// HorizontalTableViewCell.swift
// HorizontalTables
import UIKit
class HorizontalTableViewCell: UITableViewCell, UITableViewDelegate, UITableViewDataSource {
var horizontalTableView: UITableView!
var articles: NSArray!
override init(frame: CGRect) {
super.init(frame: frame)
var frameA: CGRect = CGRectMake(0, 0, 106, 320)
horizontalTableView = UITableView(frame: frameA)
horizontalTableView.showsVerticalScrollIndicator = false
horizontalTableView.showsHorizontalScrollIndicator = false
horizontalTableView.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI) * 0.5)
horizontalTableView.frame = CGRectMake(0, 0, 320, 106)
horizontalTableView.rowHeight = 106
horizontalTableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine
horizontalTableView.separatorColor = UIColor.clearColor()
horizontalTableView.backgroundColor = UIColor(red: 201/252, green: 235/252, blue: 245/252, alpha: 1)
horizontalTableView.dataSource = self
horizontalTableView.delegate = self
self.addSubview(horizontalTableView)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return articles.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ArticleCell", forIndexPath: indexPath) as ArticleTableViewCell
let currentArticle: NSDictionary = articles.objectAtIndex(indexPath.row) as NSDictionary
cell.titleLabel.text = currentArticle.objectForKey("Title") as NSString
var pics: AnyObject = currentArticle.objectForKey("ImageName")!
cell.imageView?.image = UIImage(named: "\(pics)")
return cell
}
}
// ArticleListViewController.swift
// HorizontalTables
import UIKit
class ArticleListViewController: UITableViewController {
var articleDictionary: NSDictionary = NSDictionary()
let kHeadlineSectionHeight: Int = 26
let kRegularSectionHeight: Int = 26
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = 45
let path = NSBundle.mainBundle().pathForResource("Articles", ofType: "plist")
UITableViewHeaderFooterView.appearance().tintColor = UIColor(red: 201/252, green: 235/252, blue: 245/252, alpha: 1)
articleDictionary = NSDictionary(contentsOfFile: path!)!
}
override func awakeFromNib() {
super.awakeFromNib()
self.tableView.backgroundColor = UIColor(red: 201/252, green: 235/252, blue: 245/252, alpha: 1)
tableView.rowHeight = 106
}
override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return articleDictionary.allKeys.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let keys: NSArray = articleDictionary.allKeys
var sortedCategories: NSArray = keys.sortedArrayUsingSelector("localizedCompare:")
var categoryName: NSString = sortedCategories.objectAtIndex(section) as NSString
return categoryName.substringFromIndex(1)
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as HorizontalTableViewCell
let keys: NSArray = articleDictionary.allKeys
var sortedCategories: NSArray = keys.sortedArrayUsingSelector("localizedCompare:")
var categoryName: NSString = sortedCategories.objectAtIndex(indexPath.section) as NSString
let currentCategory: NSArray = articleDictionary.objectForKey(categoryName) as NSArray
cell.articles = currentCategory
if (indexPath.row % 2) == 1 {
cell.backgroundColor = UIColor(red: 201/252, green: 235/252, blue: 245/252, alpha: 1)
} else {
cell.backgroundColor = UIColor.clearColor()
}
return cell
}
}
Any thoughts on why I have tableView cells with no data?
It looks like you didn't register your cells to your tableview. If you're using a Storyboard, you need to enter in the cell identifier there. If XIBs, you'll need to register the class to the tableview first:
tableView.registerClass(ArticleTableViewCell.self, forCellReuseIdentifier: "ArticleCell")
The UITableViewController will be initialized. In the meanwhile, the delegate method and data sources methods are called for initializing purpose. That moment, you will only get what you have from your prototype of UITableViewCell. So, you have to call reloadData after you successfully distribute the data.
The problem is that you are using dequeueReusableCellWithIdentifier. It doesn't even use HorizontalTableViewCell(frame:CGRect) to init a TableViewCell, unless you manually do so. You should do something like this:
var cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as? HorizontalTableViewCell
if cell == nil {
cell = HorizontalTableViewCell(frame: CGRectMake(0, 0, tableView.frame.size.width, tableView.frame.size.height))
}
Same for ArticleTableViewCell:
var cell = tableView.dequeueReusableCellWithIdentifier("ArticleCell", forIndexPath: indexPath) as? ArticleTableViewCell
if cell == nil {
cell = ArticleTableViewCell(frame: CGRectMake(0, 0, 106, 106))
}
You can try using this init function in your cell
initWithStyle:reuseIdentifier:

What makes the searchbar option selectedScopeButtonIndexDidChange execute?

I'm trying to learn the new UISearchController in swift. Not too many examples out there yet.
I have been looking at adding a scope option such that when a user selects the scope in the search the list is filtered by the scope. I've been looking at the programming iOS 8 O'Reilly book, examples. They have an example where the filtering is done in one screen.
I realize that the actual search will not work with the current scope values I have. All I'm after at the moment is to get that function working. I'm using this code to test ideas and then porting those ideas to my application.
If I enter a text search it all works, but when I try the scope, it doesn't. Here is the code. Any hints as to why the following code is never called:
func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
println("In selectedScopeButtonIndexDidChange")
updateSearchResultsForSearchController(searcher)
}
Here is the complete code.
import UIKit
class ViewController: UITableViewController, UISearchBarDelegate, UISearchControllerDelegate, UISearchResultsUpdating {
var sectionNames = [String]()
var sectionData = [[String]]()
var originalSectionNames = [String]()
var originalSectionData = [[String]]()
var searcher = UISearchController()
var searching = false
override func prefersStatusBarHidden() -> Bool {
return true
}
override func viewDidLoad() {
//super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let s = NSString(contentsOfFile: NSBundle.mainBundle().pathForResource("states", ofType: "txt")!, encoding: NSUTF8StringEncoding, error: nil)!
let states = s.componentsSeparatedByString("\n") as [String]
var previous = ""
for aState in states {
// get the first letter
let c = (aState as NSString).substringWithRange(NSMakeRange(0,1))
// only add a letter to sectionNames when it's a different letter
if c != previous {
previous = c
self.sectionNames.append( c.uppercaseString )
// and in that case also add new subarray to our array of subarrays
self.sectionData.append( [String]() )
}
sectionData[sectionData.count-1].append( aState )
}
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
self.tableView.registerClass(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "Header")
self.tableView.sectionIndexColor = UIColor.whiteColor()
self.tableView.sectionIndexBackgroundColor = UIColor.redColor()
// self.tableView.sectionIndexTrackingBackgroundColor = UIColor.blueColor()
// self.tableView.backgroundColor = UIColor.yellowColor()
self.tableView.backgroundView = { // this will fix it
let v = UIView()
v.backgroundColor = UIColor.yellowColor()
return v
}()
// in this version, we take the total opposite approach:
// we don't present any extra view at all!
// we already have a table, so why not just filter the very same table?
// to do so, pass nil as the search results controller,
// and tell the search controller not to insert a dimming view
// keep copies of the original data
self.originalSectionData = self.sectionData
self.originalSectionNames = self.sectionNames
let searcher = UISearchController(searchResultsController:nil)
self.searcher = searcher
searcher.dimsBackgroundDuringPresentation = false
searcher.searchResultsUpdater = self
searcher.delegate = self
// put the search controller's search bar into the interface
let b = searcher.searchBar
b.sizeToFit() // crucial, trust me on this one
b.autocapitalizationType = .None
b.scopeButtonTitles = ["All", "S", "X"] // won't show in the table
**b.delegate = self**
self.tableView.tableHeaderView = b
self.tableView.reloadData()
//self.tableView.scrollToRowAtIndexPath(
// NSIndexPath(forRow: 0, inSection: 0),
// atScrollPosition:.Top, animated:false)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.sectionNames.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.sectionData[section].count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let s = self.sectionData[indexPath.section][indexPath.row]
cell.textLabel!.text = s
// this part is not in the book, it's just for fun
var stateName = s
stateName = stateName.lowercaseString
stateName = stateName.stringByReplacingOccurrencesOfString(" ", withString:"")
stateName = "flag_\(stateName).gif"
let im = UIImage(named: stateName)
cell.imageView!.image = im
return cell
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let h = tableView.dequeueReusableHeaderFooterViewWithIdentifier("Header") as UITableViewHeaderFooterView
if h.tintColor != UIColor.redColor() {
h.tintColor = UIColor.redColor() // invisible marker, tee-hee
h.backgroundView = UIView()
h.backgroundView!.backgroundColor = UIColor.blackColor()
let lab = UILabel()
lab.tag = 1
lab.font = UIFont(name:"Georgia-Bold", size:22)
lab.textColor = UIColor.greenColor()
lab.backgroundColor = UIColor.clearColor()
h.contentView.addSubview(lab)
let v = UIImageView()
v.tag = 2
v.backgroundColor = UIColor.blackColor()
v.image = UIImage(named:"us_flag_small.gif")
h.contentView.addSubview(v)
lab.setTranslatesAutoresizingMaskIntoConstraints(false)
v.setTranslatesAutoresizingMaskIntoConstraints(false)
h.contentView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat("H:|-5-[lab(25)]-10-[v(40)]",
options:nil, metrics:nil, views:["v":v, "lab":lab]))
h.contentView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat("V:|[v]|",
options:nil, metrics:nil, views:["v":v]))
h.contentView.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat("V:|[lab]|",
options:nil, metrics:nil, views:["lab":lab]))
}
let lab = h.contentView.viewWithTag(1) as UILabel
lab.text = self.sectionNames[section]
return h
}
// much nicer without section index during search
override func sectionIndexTitlesForTableView(tableView: UITableView) -> [AnyObject]! {
return self.searching ? nil : self.sectionNames
}
func searchBar(searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
println("In selectedScopeButtonIndexDidChange")
updateSearchResultsForSearchController(searcher)
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
let sb = searchController.searchBar
let target = sb.text
println("target is: \(target)")
if target == "" {
self.sectionNames = self.originalSectionNames
self.sectionData = self.originalSectionData
self.tableView.reloadData()
return
}
// we have a target string
self.sectionData = self.originalSectionData.map {
$0.filter {
let options = NSStringCompareOptions.CaseInsensitiveSearch
let found = $0.rangeOfString(target, options: options)
return (found != nil)
}
}.filter {$0.count > 0} // is Swift cool or what?
self.sectionNames = self.sectionData.map {prefix($0[0],1)}
self.tableView.reloadData()
}
}
The answer to this problem was to add:
b.delegate = self
into the viewDidLoad() section.
I've updated the code above with the new line added.
You will need to set the searchBar delegate explicitly
// put the search controller's search bar into the interface
let b = searcher.searchBar
b.delegate = self

Resources