I'm writing a code for a 'checklist' creator'. There are several parts in the application. One is for creating a new checklist from scratch. Based on the choices the user makes a row of a checklist will have a checkbox, input field or yes/no segmentswitch. I managed to set this all up using Parse, but there is one problem.
When I click on a button, a new row is added and I can enter some date here.
Then I can click that same button again and the same happens etc.
However, when I'm adding my fifth row, the fifth row will become the current first row and the first row is now an empty one. I've added the code below. It's quitte long due to all different variable's, but I think the most important ones are the
'add' IBaction and the part where the table view is configured. Can someone help me solving my problem?
Thanks,
Sven
Picture: This is what the mistake looks like. The last row is actually the one I configured as first row. The first row is now empty (off screen)
import UIKit
import Parse
import Foundation
var emptyone = true
class Homescreen: UITableViewController, UITextFieldDelegate {
var topField = [""]
var first = Bool()
var label1 = [""]
var switchEnab = [false]
var fieldEnab = [false]
var yesnoEnab = [false]
var topSegmentEnab = [false]
var topFieldEnab = [false]
var segmentName0 = [""]
var segmentName1 = [""]
var segmentName2 = [""]
var messageEnab = [false]
var message1 = [""]
var labelEnab = [false]
var field1 = [""]
var switch1 = [false]
var topSegment = [1]
var yesno1 = [1]
var nummer = Int()
// Add a new row
#IBAction func add(sender: AnyObject) {
first = true
self.label1.append("" as String!)
self.topField.append("" as String!)
self.segmentName0.append("" as String!)
self.segmentName1.append("" as String!)
self.segmentName2.append("" as String!)
self.message1.append("" as String!)
self.field1.append("" as String!)
self.switchEnab.append(false)
self.fieldEnab.append(false)
self.topFieldEnab.append(false)
self.yesnoEnab.append(false)
self.topSegmentEnab.append(false)
self.messageEnab.append(false)
self.labelEnab.append(false)
self.switch1.append(false)
self.yesno1.append(1)
self.topSegment.append(1)
self.tableView.reloadData()
}
#IBOutlet var celOpmaak: UITableView!
// Function initators
var activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
func busy(){
activityIndicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 50, 50))
activityIndicator.center = self.view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
UIApplication.sharedApplication().beginIgnoringInteractionEvents()
}
func displayAlert(title: String, message: String){
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: { (action) in
}))
self.presentViewController(alert, animated: true, completion: nil)
}
// Final save of the checklist
#IBAction func saveButton(sender: AnyObject) {
if last == true{
dispatch_async(dispatch_get_main_queue()) {
self.first = false
self.tableView.reloadData()
}
busy()
let newValues = PFObject(className: "clTypes")
newValues["taskName"] = ""
newValues["dateStamp"] = " - - "
newValues["topSegmentEnab"] = self.topSegmentEnab
newValues["segmentName0"] = self.segmentName0
newValues["segmentName1"] = self.segmentName1
newValues["segmentName2"] = self.segmentName2
newValues["topFieldEnab"] = self.topFieldEnab
newValues["topField"] = self.topField
newValues["label1"] = self.label1
newValues["label1Enab"] = self.labelEnab
newValues["fieldEnab"] = self.fieldEnab
newValues["field1"] = self.field1
newValues["switchEnab"] = self.switchEnab
newValues["switch1"] = self.switch1
newValues["yesnoEnab"] = self.yesnoEnab
newValues["yesno1"] = self.yesno1
newValues["topSegment"] = self.topSegment
newValues["messageEnab"] = self.messageEnab
newValues["message1"] = self.message1
newValues["clType"] = clType1
newValues["checklistTitle"] = checklistTitle1
newValues.saveInBackground()
self.activityIndicator.stopAnimating()
UIApplication.sharedApplication().endIgnoringInteractionEvents()
self.displayAlert("Checklist saved", message: "The checklist has been saved to the server succesfully")
self.dismissViewControllerAnimated(true, completion: nil)
self.tableView.reloadData()
self.performSegueWithIdentifier("overview", sender: self)
}else{
displayAlert("No final button", message: "Please add a 'last' cell to the checklist")
}
}
// VIEWDIDLOAD
override func viewDidLoad() {
super.viewDidLoad()
self.label1.removeAll(keepCapacity: true)
self.field1.removeAll(keepCapacity: true)
self.message1.removeAll(keepCapacity: true)
self.topField.removeAll(keepCapacity: true)
self.segmentName0.removeAll(keepCapacity: true)
self.segmentName1.removeAll(keepCapacity: true)
self.segmentName2.removeAll(keepCapacity: true)
self.switchEnab.removeAll(keepCapacity: true)
self.yesnoEnab.removeAll(keepCapacity: true)
self.topFieldEnab.removeAll(keepCapacity: true)
self.fieldEnab.removeAll(keepCapacity: true)
self.messageEnab.removeAll(keepCapacity: true)
self.labelEnab.removeAll(keepCapacity: true)
self.switch1.removeAll(keepCapacity: true)
self.topSegmentEnab.removeAll(keepCapacity: true)
self.yesno1.removeAll(keepCapacity: true)
self.topSegment.removeAll(keepCapacity: true)
last = false
//var helloWorldTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("sayHello"), userInfo: nil, repeats: true)
toeVoegen.wraps = false
toeVoegen.autorepeat = false
toeVoegen.maximumValue = 80
self.tableView.reloadData()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return label1.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell! = tableView.dequeueReusableCellWithIdentifier("cel", forIndexPath: indexPath) as! creatorCellTableViewCell
if cell == nil{
print("hoi")
let cell = tableView.dequeueReusableCellWithIdentifier("cel", forIndexPath: indexPath) as! creatorCellTableViewCell
if first == true{
cell.option1.delegate = self
func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.view.endEditing(true)
}
print(self.label1)
print(Int(toeVoegen.value))
label1[indexPath.row] = cell.labelField.text!
}else{
// This part is only called when the 'final save button' has been activated
label1[indexPath.row] = cell.labelField.text!
field1[indexPath.row] = ""
yesno1[indexPath.row] = 1
topField[indexPath.row] = ""
// switchEnab, fieldEnab, yesnoEnab
if cell.degreeField.selectedSegmentIndex == 1{
labelEnab[indexPath.row]=false
topFieldEnab[indexPath.row]=true
topSegmentEnab[indexPath.row]=true
segmentName0[indexPath.row] = ""
segmentName1[indexPath.row] = ""
segmentName2[indexPath.row] = ""
if cell.typeField.selectedSegmentIndex == 0{
switchEnab[indexPath.row] = false
fieldEnab[indexPath.row] = true
yesnoEnab[indexPath.row] = true
messageEnab[indexPath.row]=false
message1[indexPath.row]=cell.messageField.text!
}else if cell.typeField.selectedSegmentIndex == 1{
switchEnab[indexPath.row] = true
fieldEnab[indexPath.row] = true
yesnoEnab[indexPath.row] = false
if cell.messageSwitch.on == true{
messageEnab[indexPath.row]=cell.messageSwitch.on
message1[indexPath.row]=cell.messageField.text!
}else{
messageEnab[indexPath.row]=false
message1[indexPath.row]="hallo"
}
}else if cell.typeField.selectedSegmentIndex == 2{
labelEnab[indexPath.row]=false
switchEnab[indexPath.row] = true
fieldEnab[indexPath.row] = false
yesnoEnab[indexPath.row] = true
messageEnab[indexPath.row]=false
message1[indexPath.row]=""
}
}else if cell.degreeField.selectedSegmentIndex == 0{
switchEnab[indexPath.row]=true
fieldEnab[indexPath.row]=true
yesnoEnab[indexPath.row]=true
labelEnab[indexPath.row]=true
messageEnab[indexPath.row]=false
message1[indexPath.row]=""
if cell.topVeld.selectedSegmentIndex == 0{
topSegmentEnab[indexPath.row] = false
topFieldEnab[indexPath.row] = true
segmentName0[indexPath.row] = cell.option1.text!
segmentName1[indexPath.row] = cell.option2.text!
segmentName2[indexPath.row] = cell.option3.text!
} else{
topSegmentEnab[indexPath.row]=true
topFieldEnab[indexPath.row]=false
}
}
}
}
return cell
}
Most likely this is related to the fact that you are reusing cells. Every time you do this:
let cell = tableView.dequeueReusableCellWithIdentifier("cel",
forIndexPath: indexPath) as! creatorCellTableViewCell
there is a chance (a very likely chance as soon as you have more rows than fit on your screen) that a different cell is returned than was originally at that index. For example, as soon as you scroll up to row #5 and row #1 is no longer on the screen you could be getting the same exact cell that was at row #1 being displayed at row #5. When you are reusing cells you have to set the state on the cell every single time. For example:
let cell = tableView.dequeueReusableCellWithIdentifier("cel",
forIndexPath: indexPath) as! creatorCellTableViewCell
cell.labelField.text! = label1[indexPath.row]
// do this for every field that needs to be set
Related
The error:
[Assert] Surprise! Activating a search controller whose navigation item is not at the top of the stack. This case needs examination in UIKit. items = (null),
search hosting item = <UINavigationItem: 0x1068473a0> title='PARTS' style=navigator leftBarButtonItems=0x282bfb8d0 rightBarButtonItems=0x282bfb890 searchController=0x110024400 hidesSearchBarWhenScrolling
Why am I getting this error and how do I fix it? This question is similar to another post, but there was only one response to it and the response was not detailed at all (therefore not helpful).
import UIKit
import SPStorkController
struct Part {
var title: String?
var location: String?
var selected: Bool? = false
}
class InspectorViewController: UIViewController, UINavigationControllerDelegate, UITableViewDataSource, UITableViewDelegate, UISearchResultsUpdating, UISearchBarDelegate {
private let initials = InspectorPartsList.getInitials() // model
private var parts = InspectorPartsList.getDamageUnrelatedParts() // model
var filteredParts: [Part] = [] // model
var searching = false
var searchController = UISearchController(searchResultsController: nil)
lazy var searchBar: UISearchBar = UISearchBar()
var isSearchBarEmpty: Bool {
return searchController.searchBar.text?.isEmpty ?? true
}
var navBar = UINavigationBar()
let inspectorTableView = UITableView() // tableView
var darkTheme = Bool()
override func viewDidLoad() {
super.viewDidLoad()
// setup the navigation bar
setupNavBar()
// add the table view
setupInspectorTableView()
// add the search controller to the navigation bar
setupSearchController()
}
func setupNavBar() {
navBar = UINavigationBar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 100))
view.addSubview(navBar)
let navItem = UINavigationItem(title: "PARTS")
let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: #selector(self.addBtnTapped))
let cancelItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: nil, action: #selector(self.cancelBtnTapped))
navItem.rightBarButtonItem = doneItem
navItem.leftBarButtonItem = cancelItem
navItem.searchController = searchController
navBar.setItems([navItem], animated: false)
}
#objc func cancelBtnTapped() {
// dismiss the storkView
SPStorkController.dismissWithConfirmation(controller: self, completion: nil)
}
#objc func addBtnTapped() {
// get all of the selected rows
// Update the InspectionData model with the selected items... this will allow us to update the InspectionTableView in the other view
// create an empty array for the selected parts
var selectedParts = [Part]()
// loop through every selected index and append it to the selectedParts array
for part in parts {
if part.selected! {
selectedParts.append(part)
}
}
// update the InspectionData model
if !selectedParts.isEmpty { // not empty
InspectionData.sharedInstance.partsData?.append(contentsOf: selectedParts)
// update the inspectionTableView
updateInspectionTableView()
}
// dismiss the storkView
SPStorkController.dismissWithConfirmation(controller: self, completion: nil)
}
func cancelAddPart() {
// dismiss the storkView
SPStorkController.dismissWithConfirmation(controller: self, completion: nil)
}
private func setupInspectorTableView() {
// set the data source
inspectorTableView.dataSource = self
// set the delegate
inspectorTableView.delegate = self
// add tableview to main view
view.addSubview(inspectorTableView)
// set constraints for tableview
inspectorTableView.translatesAutoresizingMaskIntoConstraints = false
// inspectorTableView.topAnchor.constraint(equalTo: fakeNavBar.bottomAnchor).isActive = true
inspectorTableView.topAnchor.constraint(equalTo: navBar.bottomAnchor).isActive = true
inspectorTableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
inspectorTableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
inspectorTableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
// allow multiple selection
inspectorTableView.allowsMultipleSelection = true
inspectorTableView.allowsMultipleSelectionDuringEditing = true
// register the inspectorCell
inspectorTableView.register(CheckableTableViewCell.self, forCellReuseIdentifier: "inspectorCell")
}
func setupSearchController() {
// add the bar
searchController.searchResultsUpdater = self
searchController.searchBar.delegate = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search by part name or location"
definesPresentationContext = true
searchController.searchBar.sizeToFit()
self.inspectorTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return filteredParts.count
} else {
return parts.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let inspectorCell = tableView.dequeueReusableCell(withIdentifier: "inspectorCell", for: indexPath)
var content = inspectorCell.defaultContentConfiguration()
var part = Part()
if searching {
// showing the filteredParts array
part = filteredParts[indexPath.row]
if filteredParts[indexPath.row].selected! {
// selected - show checkmark
inspectorCell.accessoryType = .checkmark
} else {
// not selected
inspectorCell.accessoryType = .none
}
} else {
// showing the parts array
part = parts[indexPath.row]
if part.selected! {
// cell selected - show checkmark
inspectorCell.accessoryType = .checkmark
} else {
// not selected
inspectorCell.accessoryType = .none
}
}
content.text = part.title
content.secondaryText = part.location
inspectorCell.contentConfiguration = content
return inspectorCell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Note: When you select or unselect a part in the filteredParts array, you must also do so in the parts array
if searching { // using filteredParts array
if filteredParts[indexPath.row].selected! { // selected
filteredParts[indexPath.row].selected = false // unselect the part
// search the parts array for the part by both the title and location, so we for sure get the correct part (there could be parts with identical titles with different locations)
if let part = parts.enumerated().first(where: { $0.element.title == filteredParts[indexPath.row].title && $0.element.location == filteredParts[indexPath.row].location}) { // exact part (with same title & location) found
parts[part.offset].selected = false // unselect the part
}
} else { // not selected
filteredParts[indexPath.row].selected = true // select the part
if let part = parts.enumerated().first(where: { $0.element.title == filteredParts[indexPath.row].title && $0.element.location == filteredParts[indexPath.row].location}) { // exact part (with same title & location) found
parts[part.offset].selected = true // select the part
}
}
} else { // using parts array
if parts[indexPath.row].selected! { // selected
parts[indexPath.row].selected = false // unselect the part
} else { // not selected
parts[indexPath.row].selected = true // select the part
}
}
inspectorTableView.reloadRows(at: [indexPath], with: .none) // reload the tableView
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredParts = parts.filter { ($0.title?.lowercased().prefix(searchText.count))! == searchText.lowercased() }
searching = true
inspectorTableView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = ""
inspectorTableView.reloadData()
}
func updateSearchResults(for searchController: UISearchController) {
}
private func updateInspectionTableView() {
NotificationCenter.default.post(name: NSNotification.Name("updateInspectionTable"), object: nil)
}
}
// CHECKABLE UITABLEVIEWCELL
class CheckableTableViewCell: UITableViewCell {
}
I'm getting record from server using php
As i'm getting 11 record currently so i want in starting i will show just 6 records and remaining next 5 record will show when user reached at last cell while scrolling. So this process is in working form, but the problem is while running, it working so fast that before reaching last row all records are already showing while scrolling and the activity indicator is just animating at the bottom of tableView.
I don't know what is the problem.
Also i want when user reached at last cell, activity indicator start animating while loading data .
Here is my code
import UIKit
import AlamofireImage
struct property{
let property_Id : String
let propertyTitle : String
let imageURL : String
let user_Id : String
let area : String
let bed : String
let unit : String
let bath : String
let price : String
}
class searchRecordsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource,favouriteButtonTableViewCellDelegate {
var y = 6
var m = 12
#IBOutlet weak var tableView : UITableView!
var RESULT : [NSDictionary] = []
var myProperty = [property]()
var myPropertyCopy = [property]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//MARK: Getting dictionary data from previous controller
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.title = "Results"
self.navigationController?.navigationBar.tintColor = UIColor.black
//MARK: Getting dictionary data from previous controller
for item in RESULT {
let propertyInfo = property(property_Id: String(item["propertyId"]! as! Int), propertyTitle: item["propertyTitle"]! as! String, imageURL: item["imagePath"]! as! String, user_Id: String(item["userId"]! as! Int), area: item["area"]! as! String, bed: item["bed"]! as! String, unit: item["unit"]! as! String, bath: item["bath"]! as! String, price: item["price"]! as! String )
myProperty.append(propertyInfo)
}
//MARK: Inserting first 6 records in Array
for i in 0 ..< 6 {
if !(myProperty.indices.contains(i)) {
break
}
myPropertyCopy.append(myProperty[i])
}
}
func downloadImage(imagePath : String, theIMAGEVIEW : UIImageView) {
let myUrl = URL(string: URL_IP+imagePath);
//MARK: AlamofireImage to download the image
theIMAGEVIEW.af_setImage(withURL: myUrl!, placeholderImage: #imageLiteral(resourceName: "addProperty"), filter: nil, progress: nil, runImageTransitionIfCached: true, completion: nil)
}
func tableView(_ tableView:UITableView, numberOfRowsInSection section:Int) -> Int
{
return myPropertyCopy.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "RecordCell") as! searchrecordsTableViewCell
let property = myPropertyCopy[indexPath.row]
cell.area.text = property.area+" "+property.unit
if property.bath == ""{
cell.bath.text = property.bath
}
else{
cell.bath.text = property.bath+" Baths"
}
if property.bed == ""{
cell.bed.text = property.bed
}
else{
cell.bed.text = property.bed+" Baths"
}
cell.propertyTitle.text = property.propertyTitle
cell.price.text = convertAMOUNT(price : property.price)
downloadImage(imagePath: property.imageURL, theIMAGEVIEW: cell.myImageView)
//----
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if myPropertyCopy.count != myProperty.count{
let lastRow = myPropertyCopy.count - 1
if indexPath.row == lastRow {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
spinner.startAnimating()
spinner.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))
self.tableView.tableFooterView = spinner
self.tableView.tableFooterView?.isHidden = false
moreData()
}
}
}
func moreData(){
for i in y ..< m {
if !(myProperty.indices.contains(i)) {
break
}
myPropertyCopy.append(myProperty[i])
}
y = y + 10
m = m + 10
self.tableView.reloadData()
}
}
currently my TableView looks like
See output here
Thanks in Advance.
After a long practice i have done my problem, i have just added UIScrollView delegate function for checking if the user reached at last cell then start activityIndicator for 3 seconds and then load data and that's it.
As i have just very small amount of data that's why i'm start ing UIactivityIndicator for 3 seconds before getting data.
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
// UITableView only moves in one direction, y axis
let currentOffset = scrollView.contentOffset.y
let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height
if maximumOffset - currentOffset <= 10.0 {
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
if myPropertyCopy.count != myProperty.count {
//print("this is the last cell")
spinner.startAnimating()
spinner.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: tableView.bounds.width, height: CGFloat(44))
spinner.hidesWhenStopped = true
self.tableView.tableFooterView = spinner
self.tableView.tableFooterView?.isHidden = false
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
//MARK: Loading more data
self.moreData()
}
}
else{
self.tableView.tableFooterView?.isHidden = true
spinner.stopAnimating()
}
}
}
I am having hard time resolving one issue. I have a tableview which has many prototype cells. One of them i am using a tagview to show multiple tags selected. So i have used a XIB file which has a TAGVIEW as subview of it & I have used that XIB in tableview cell. Now when i first load the tableview and scroll down then height of cell is large but when i scroll down and up then it's fits in the size of tags. I have tried below solutions for the but not of them worked.
I tried solutions:
1.cell.layoutIfNeeded before return cell.
2.cell.layoutSubView
3.cell.tagView.layoutIfNeeded
4.cell.setNeedsLayout()
5.Relaod tableview in ViewDidAppear
6.In tagViewCellXIB, in AwakeForNib method added self.layoutIfNeeded.
7.In tagViewCellXIB,Override didMoveToParent() & added self.layoutIfNeeded.
I have given estimated row height already.
Here is tagViewCell Class
class TagViewCell: UITableViewCell {
#IBOutlet weak var tagView: TagListView!
#IBOutlet weak var textfield: UITextField!
#IBOutlet weak var titleLbl: UILabel!
#IBOutlet weak var heightTagViewConstraint : NSLayoutConstraint!
#IBOutlet weak var lblValidation: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
self.layoutIfNeeded()
// Initialization code
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
self.layoutIfNeeded()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
self.heightTagViewConstraint.constant = 35
}
func configureView(type: TagCellType){
switch type {
case .Language:
setupLanguageCell()
titleLbl.text = "Language"
textfield.isHidden = true
if UserData.userData.language.count == 0{
tagView.isHidden = true
textfield.isHidden = false
textfield.placeholder = "What languages do you speak?"
}
break
default:
textfield.isHidden = true
setupTagCell(tagType: type)
if type == .preferArea {
seUpPreffredAreaValidation()
titleLbl.text = "Preferred areas"
if UserData.userData.preferAreaArr.count == 0{
tagView.isHidden = true
textfield.isHidden = false
textfield.placeholder = "What areas do you prefer?"
}
}
else if type == .City{
titleLbl.text = "Preferred cities"
if UserData.userData.cities.count == 0{
tagView.isHidden = true
textfield.isHidden = false
textfield.placeholder = "What city do you prefer? (Optional)"
}
}else{
titleLbl.text = "Preferred countries"
if UserData.userData.country.count == 0{
tagView.isHidden = true
textfield.isHidden = false
textfield.placeholder = "What country do you prefer? (Optional)"
}
}
break
}
}
/// Set up tag view
func setupLanguageCell() {
tagView.removeAllTags()
tagView.tag = 1003 // For Language field
for (index, elememt) in UserData.userData.language.enumerated() {
print(index)
if let langDict = elememt as? NSDictionary{
tagView.isHidden = false
let languageTagView = tagView.addTag(langDict.value(forKey: "lang_name") as! String)
languageTagView.tagBackgroundColor = UIColor.clear
languageTagView.textColor = .black
languageTagView.textFont = UIFont.systemFont(ofSize: 17)
languageTagView.paddingX = -1
languageTagView.isUserInteractionEnabled = false
let levelTagView = tagView.addTag(Constants.languageLevel[langDict.value(forKey: "level") as! Int])
levelTagView.frame = CGRect(x: levelTagView.frame.origin.x, y: (languageTagView.frame.height / 2) - (levelTagView.frame.height / 2) + 2, width: levelTagView.frame.width, height: levelTagView.frame.height)
levelTagView.layoutIfNeeded()
levelTagView.layoutSubviews()
levelTagView.tagBackgroundColor = UIColor().textOrange()
levelTagView.textFont = UIFont.systemFont(ofSize: 10)
levelTagView.cornerRadius = 6
print("pading yyyyyy \(String(describing: tagView.rowViews.last?.frame.minY))")
if UserData.userData.language.count == index+1{
if (tagView.rowViews.last?.frame.minY)! == 48{
levelTagView.frame.origin.y = (languageTagView.frame.height / 2) - (levelTagView.frame.height / 2) + 8
}
}
}
}
if UserData.userData.language.count == 0 {
self.lblValidation.isHidden = false
} else {
self.lblValidation.isHidden = true
}
}
func seUpPreffredAreaValidation() {
if UserData.userData.preferAreaArr.count == 0 {
self.lblValidation.isHidden = false
} else {
self.lblValidation.isHidden = true
}
}
/// setup cell for country and cities
///
/// - Parameter tagType: type of cell country or cities
func setupTagCell(tagType:TagCellType) {
tagView.removeAllTags()
var tagArray:[String] = []
tagArray = UserData.userData.country
tagView.tag = 1001 // For countries field
if tagType == .City {
tagArray = []
tagArray = UserData.userData.cities
tagView.tag = 1002 // For city field
}
if tagType == .preferArea {
tagArray = []
tagArray = UserData.userData.preferAreaArr
tagView.tag = 1003
}
var tagValue = ""
for (_, elememt) in tagArray.enumerated() {
tagView.isHidden = false
print(elememt)
if elememt.characters.count > 17 {
let index = elememt.index(elememt.startIndex, offsetBy: 16)
tagValue = elememt.substring(to: index) + ".."
}else{
tagValue = elememt
}
let levelTagView = tagView.addTag(tagValue)
levelTagView.tagBackgroundColor = UIColor().textOrange()
tagView.textFont = UIFont.systemFont(ofSize: 17)
levelTagView.cornerRadius = 8
levelTagView.enableRemoveButton = false
levelTagView.paddingX = 6
levelTagView.paddingY = 3
}
}
}
For cellForRowAtIndexPath below code is used
let cell = self.tableView.dequeueReusableCell(withIdentifier: "tagcell", for: indexPath) as! TagViewCell
cell.configureView(type: .City)
cell.tagView.delegate = self
if UserData.userData.cities.count >= 0 && UserData.userData.cities.count <= 3 {
cell.heightTagViewConstraint.constant = 25.7
}
else if let height = cell.tagView.subviews.last?.frame.maxY {
cell.heightTagViewConstraint.constant = height + 10
}
return cell
Override layoutSubviews in your TagViewCell class like bellow
class TagViewCell: UITableViewCell {
override func layoutSubviews() {
//Set the frame of tagView here
//self.tagView.frame =
}
}
and call cell.layoutIfNeeded while returning cell. If you still get any problem please comment. we are here to help you.
I'm trying to implement the comment view controller.
I want using the auto layout via the storyboard.
My question is...
When I tap the input text..then keyboard will move up...but input text is not move up..
Keyboard is overlapping the text input..
Here is TableViewController.swift
import UIKit
import Parse
var commentUUID = [String]()
var commentOwner = [String]()
class CommentViewController: UIViewController, UITextViewDelegate, UITableViewDelegate, UITableViewDataSource{
//UI Objects
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var commentTextView: UITextView!
#IBOutlet weak var sendButton: UIButton!
var refresher = UIRefreshControl()
//values for reseting UI to default
var tableViewHeight : CGFloat = 0
var commentY : CGFloat = 0
var commentHeight : CGFloat = 0
//arryas to hold server data
var usernameArray = [String]()
var profileArray = [PFFile]()
var commentArray = [String]()
var dateArray = [NSDate?]()
//variable to hold keyboard frame
var keyboard = CGRect()
//page size
var page : Int32 = 15
override func viewDidLoad() {
super.viewDidLoad()
tableView.backgroundColor = .redColor()
//title at the top
self.navigationItem.title = "COMMENTS"
self.navigationItem.hidesBackButton = true
let backButton = UIBarButtonItem(title: "back", style: .Plain, target: self, action: #selector(CommentViewController.back(_:)))
self.navigationItem.leftBarButtonItem=backButton
//swipe to go back
let backSwipe = UISwipeGestureRecognizer(target: self, action: #selector(CommentViewController.back(_:)))
backSwipe.direction=UISwipeGestureRecognizerDirection.Right
self.view.addGestureRecognizer(backSwipe)
// catch notification if the keyboard is shown or hidden
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(CommentViewController.keyboardWillShow(_:)), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(CommentViewController.keyboardWillHide(_:)), name: UIKeyboardWillHideNotification, object: nil)
//disable button from the beginning
sendButton.enabled = false
//call function
alignment()
loadComments()
}
//I think it is not affect on the layout...
func configureTableView() {
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 160.0
}
// preload func
override func viewWillAppear(animated: Bool) {
//hide bottom bar
self.tabBarController?.tabBar.hidden = true
}
// postload func
override func viewWillDisappear(animated: Bool) {
self.tabBarController?.tabBar.hidden = false
}
//func loading when keyboard is shown
func keyboardWillShow(notification : NSNotification){
//define keyboard frame size
keyboard = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey]!.CGRectValue)!
//move UI up
UIView.animateWithDuration(0.4){ () -> Void in
self.tableView.frame.size.height = self.tableViewHeight - self.keyboard.height - self.commentTextView.frame.size.height + self.commentHeight
print("keyboard show")
self.commentTextView.frame.origin.y = self.commentY - self.keyboard.height - self.commentTextView.frame.size.height + self.commentHeight
self.sendButton.frame.origin.y = self.commentTextView.frame.origin.y
self.commentTextView.frame.origin.y = 250
}
}
//func loading when keyboard is hidden
func keyboardWillHide(notification : NSNotification){
//move UI down
UIView.animateWithDuration(0.4){() -> Void in
self.tableView.frame.size.height = self.tableViewHeight
self.commentTextView.frame.origin.y = self.commentY
self.sendButton.frame.origin.y = self.commentY
}
}
//alignment function
func alignment(){
let width = self.view.frame.size.width
let height = self.view.frame.size.height
tableView.frame = CGRectMake(0, 0, width, height - self.navigationController!.navigationBar.frame.size.height)
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 160.0
commentTextView.layer.cornerRadius = commentTextView.frame.size.width / 50
//delegates
commentTextView.delegate = self
tableView.delegate = self
tableView.dataSource = self
//assign reseting values
tableViewHeight = tableView.frame.size.height
commentHeight = commentTextView.frame.size.height
commentY = commentTextView.frame.origin.y
}
//while writing something
func textViewDidChange(textView: UITextView) {
//disable button if entered no text
let spacing = NSCharacterSet.whitespaceAndNewlineCharacterSet()
//It shown when user entered type
if !commentTextView.text.stringByTrimmingCharactersInSet(spacing).isEmpty{
sendButton.enabled = true
}else {
sendButton.enabled = false
}
// + paragraph
if textView.contentSize.height > textView.frame.size.height && textView.frame.height < 130{
//find difference to add
let difference = textView.contentSize.height - textView.frame.size.height
//redefine frame of commentText
textView.frame.origin.y = textView.frame.origin.y - difference
textView.frame.size.height = textView.contentSize.height
//move up tableView
if textView.contentSize.height + keyboard.height + commentY >= tableView.frame.size.height {
tableView.frame.size.height = tableView.frame.size.height - difference
}
}
// - parapraph
else if textView.contentSize.height < textView.frame.size.height {
//find difference to deduct
let difference = textView.frame.size.height - textView.contentSize.height
//redefine frame of commentText
textView.frame.origin.y = textView.frame.origin.y + difference
textView.frame.size.height = textView.contentSize.height
//move down tableview
if textView.contentSize.height + keyboard.height + commentY > tableView.frame.size.height {
tableView.frame.size.height = tableView.frame.size.height + difference
}
}
}
//load comments function
func loadComments(){
//STEP 1. Count total comments in order to skip all except page size
let countQuery = PFQuery(className: "comments")
countQuery.whereKey("to", equalTo: commentUUID.last!)
countQuery.countObjectsInBackgroundWithBlock({(count:Int32, error:NSError?) -> Void in
//if comments on the server for current post are more than (page size 15) implement pull to refresh func
if self.page < count {
self.refresher.addTarget(self, action: #selector(CommentViewController.loadMore), forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(self.refresher)
}
//STEP 2. Request last (page size 15) comments
let query = PFQuery(className: "comments")
query.whereKey("to", equalTo: commentUUID.last!)
query.skip = count - self.page
query.addAscendingOrder("createdAt")
query.findObjectsInBackgroundWithBlock({(objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
//clean up
self.usernameArray.removeAll(keepCapacity: false)
self.profileArray.removeAll(keepCapacity: false)
self.commentArray.removeAll(keepCapacity: false)
self.dateArray.removeAll(keepCapacity: false)
//find related object
for object in objects!{
self.usernameArray.append(object.objectForKey("username") as! String)
self.profileArray.append(object.objectForKey("profileImg") as! PFFile)
self.commentArray.append(object.objectForKey("comment") as! String)
self.dateArray.append(object.createdAt)
self.tableView.reloadData()
//scroll to bottom
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: self.commentArray.count - 1, inSection: 0), atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
}
}else {
print(error?.localizedDescription)
}
})
})
}
//Pagenation
func loadMore(){
//STEP 1. Count total comments in order to skip all except page size
let countQuery = PFQuery(className: "comments")
countQuery.whereKey("to", equalTo: commentUUID.last!)
countQuery.countObjectsInBackgroundWithBlock({(count:Int32, error:NSError?) -> Void in
//self refresher
self.refresher.endRefreshing()
//remove refresher if loaded all comments
if self.page >= count {
self.refresher.removeFromSuperview()
}
//STEP2. Load more comments
if self.page < count {
//increase page to laod 30 as first paging
self.page = self.page + 15
//request existing comments from the server
let query = PFQuery(className: "comments")
query.whereKey("to", equalTo: commentUUID.last!)
query.skip = count - self.page
query.addAscendingOrder("createdAt")
query.findObjectsInBackgroundWithBlock({(objects:[PFObject]?, error:NSError?) -> Void in
if error==nil{
//clean up
self.usernameArray.removeAll(keepCapacity: false)
self.profileArray.removeAll(keepCapacity: false)
self.commentArray.removeAll(keepCapacity: false)
self.dateArray.removeAll(keepCapacity: false)
//find related objects
for object in objects! {
self.usernameArray.append(object.objectForKey("username") as! String)
self.profileArray.append(object.objectForKey("profileImg") as! PFFile)
self.commentArray.append(object.objectForKey("comments") as! String)
self.dateArray.append(object.createdAt)
self.tableView.reloadData()
}
}else {
print(error?.localizedDescription)
}
})
}
})
}
//Send Button Tapped
#IBAction func sendButtonTapped(sender: AnyObject) {
print("send tapped")
//STEP1. Add row in tableView
usernameArray.append(PFUser.currentUser()!.username!)
profileArray.append(PFUser.currentUser()?.objectForKey("profileImg") as! PFFile)
dateArray.append(NSDate())
commentArray.append(commentTextView.text.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()))
tableView.reloadData()
//STEP2. Send comment to server
let commentObj = PFObject(className: "comments")
commentObj["to"] = commentUUID.last
commentObj["username"] = PFUser.currentUser()?.username
commentObj["profileImg"] = PFUser.currentUser()?.valueForKey("profileImg")
commentObj["comment"] = commentTextView.text.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
commentObj.saveEventually()
//Scroll to bottom
self.tableView.scrollToRowAtIndexPath(NSIndexPath(forItem: commentArray.count - 1, inSection: 0), atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
//STEP 3. Reset UI
sendButton.enabled = false
commentTextView.text = ""
commentTextView.frame.size.height = commentHeight
commentTextView.frame.origin.y = sendButton.frame.origin.y
tableView.frame.size.height = self.tableViewHeight - self.keyboard.height - self.commentTextView.frame.size.height + self.commentHeight
}
//tableview
//cell numb
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return commentArray.count
}
//cell height
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
//Cell config
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
//declaire cell
let cell = tableView.dequeueReusableCellWithIdentifier("commentCell") as! CommentTableViewCell
cell.usernameButton.setTitle(usernameArray[indexPath.row], forState: .Normal)
cell.usernameButton.sizeToFit()
cell.commentLabel.text = commentArray[indexPath.row]
profileArray[indexPath.row].getDataInBackgroundWithBlock({(data:NSData?, error:NSError?) -> Void in
cell.profileImagevView.image = UIImage(data: data!)
})
//calculate date
let from = dateArray[indexPath.row]
let now = NSDate()
let components : NSCalendarUnit = [.Second, .Minute, .Hour, .Day, .WeekOfMonth]
let difference = NSCalendar.currentCalendar().components(components, fromDate: from!, toDate: now, options: [])
if difference.second <= 0 {
cell.dateLabel.text = "now"
}
if difference.second > 0 && difference.minute == 0 {
cell.dateLabel.text = "\(difference.second)s"
}
if difference.minute > 0 && difference.hour == 0 {
cell.dateLabel.text = "\(difference.minute)m"
}
if difference.hour > 0 && difference.day == 0 {
cell.dateLabel.text = "\(difference.hour)h"
}
if difference.day > 0 && difference.weekOfMonth == 0 {
cell.dateLabel.text = "\(difference.day)d."
}
if difference.weekOfMonth > 0 {
cell.dateLabel.text = "\(difference.weekOfMonth)w."
}
cell.usernameButton.layer.setValue(indexPath, forKey: "index")
return cell
}
//clicked username button
#IBAction func usernameButtonTapped(sender: AnyObject) {
//call index of current button
let i = sender.layer.valueForKey("index") as! NSIndexPath
//Call cell to call further cell data
let cell = tableView.cellForRowAtIndexPath(i) as! CommentTableViewCell
//if user tapped on his username go home, else go guest
if cell.usernameButton.titleLabel?.text == PFUser.currentUser()?.username {
let home = self.storyboard?.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
self.navigationController?.pushViewController(home, animated: true)
}else {
guestname.append(cell.usernameButton.titleLabel!.text!)
let guest = self.storyboard?.instantiateViewControllerWithIdentifier("GuestHomeViewController") as! GuestHomeViewController
self.navigationController?.pushViewController(guest, animated: true)
}
}
//go back
func back(sender : UIBarButtonItem){
//push back
self.navigationController?.popViewControllerAnimated(true)
//clean comment uuid from holding information
if !commentUUID.isEmpty{
commentUUID.removeLast()
}
//clean comment owner from last holding information
if !commentOwner.isEmpty{
commentOwner.removeLast()
}
}
}
And It is my cell controller
import UIKit
class CommentTableViewCell: UITableViewCell {
#IBOutlet weak var profileImagevView: UIImageView!
#IBOutlet weak var usernameButton: UIButton!
#IBOutlet weak var commentLabel: UILabel!
#IBOutlet weak var dateLabel: UILabel!
//default func
override func awakeFromNib() {
super.awakeFromNib()
//round profile
profileImagevView.layer.cornerRadius = profileImagevView.frame.size.width/2
profileImagevView.clipsToBounds = true
}
}
Two comments:
Use UIKeyboardDidShowNotification instead of UIKeyboardWillShowNotification
Do not modify frame directly when you're using auto layout. Just link your bottom layout constraint to the controller and change a constant value when needed.
Try this:
self.tableView.frame.size.height = self.view.frame.height - keyboard.height - self.tableView.frame.size.height
print("keyboard show")
self.commentTextView.frame = CGRect(self.commentTextView.frame.minX, self.commentTextView.frame.minY - keyboard.height, self.commentTextView.frame.width, self.commentTextView.frame.height)
self.sendButton.frame.origin.y = self.commentTextView.frame.origin.y
Edit
#Arsen's Answer makes much more sense and probably much easier, by the way :P But this should work the same.
I have UITableViewCells that contains a custom button, which is an image. When the button is pressed, the image changes. However, when I scroll up and down the tableView, the cell that is tapped no longer displays the right image, or the "tapped" image is displaying elsewhere. I understand this problem is related to the cells being dequeued and reused. What I've tried to do is create a delegate in my custom cell class. The delegate calls a method in the tableViewController class. I am doing this with the code
self.delegate?.pressedButtonInCell?(self)
However this is not working..why?
=======custom cell class====
import UIKit
#objc protocol BoostCellDelegate{
optional func pressedButtonInCell(cell:BoostCell)
optional func displayAlert (title:String , error: String)
}
class BoostCell: UITableViewCell {
var delegate:BoostCellDelegate?
var boosted = false
var boostedButton = UIImage(named: "boostButtons.png")
#IBOutlet var userImage: UIImageView!
#IBOutlet var name: UILabel!
#IBOutlet var createdAt: UILabel!
#IBOutlet var boostButton: UIButton!
#IBAction func boostPressed(sender: UIButton) {
let objectId = sender.titleLabel!.text!
var query = PFQuery(className:"boostPost")
query.getObjectInBackgroundWithId(objectId) {
(boostPost: PFObject!, error: NSError!) -> Void in
if error != nil {
NSLog("%#", error)
self.delegate?.displayAlert?("Something's gone wrong", error: "Might be the bad reception")
} else {
if boostPost != nil{
if boostPost["boostedBy"] != nil {
var boostedByArray : NSArray = boostPost["boostedBy"] as NSArray
if boostedByArray.containsObject(PFUser.currentUser().username){
println("username already boosted")
}
else{
var boostNumber = boostPost["boostNumber"] as Int
boostNumber++
boostPost["boostNumber"] = boostNumber
boostPost.addObject(PFUser.currentUser().username, forKey:"boostedBy")
boostPost.saveInBackground()
println("new username added to boosted")
self.delegate?.pressedButtonInCell?(self)
}
}
else if boostPost["boostedBy"] == nil{
var boostNumber = boostPost["boostNumber"] as Int
boostNumber++
boostPost["boostNumber"] = boostNumber
boostPost.addObject(PFUser.currentUser().username, forKey:"boostedBy")
boostPost.saveInBackground()
println("new username added to boosted")
self.delegate?.pressedButtonInCell?(self)
=====================================code in tableViewController class =========
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as BoostCell
cell.name.text = userNewNames[indexPath.row]
cell.content.text = userNewContent[indexPath.row]
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
cell.createdAt.text = userCreatedAt[indexPath.row]
cell.delegate = self
if userBoostedBy[indexPath.row].count == 2 {
cell.boostedBy.text = "1 boost"
}
else if userBoostedBy[indexPath.row].count > 2 {
var count = userBoostedBy[indexPath.row].count - 1
cell.boostedBy.text = "\(count) boosts"
}
else {
cell.boostedBy.text = "0 boost"
}
if (userButtonState[indexPath.row] == true) {
cell.boostButton.setImage(self.boostedButton, forState: UIControlState.Normal)
}
else {
cell.boostButton.setImage(self.unBoostedButton, forState: UIControlState.Normal)
}
userImagesFiles[indexPath.row].getDataInBackgroundWithBlock({ (imageData:NSData!, error:NSError!) in
if error == nil {
let image = UIImage(data: imageData)
cell.userImage.image = image
}
})
cell.boostButton.setTitle(objectIds[indexPath.row], forState: UIControlState.Normal)
func pressedButtonInCell(cell:BoostCell)
{
cell.boostButton.setImage(self.boostedButton, forState: UIControlState.Normal)
}
// function to display the alert message
func displayAlert (title:String , error: String){
var alert = UIAlertController(title: title, message: error, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: {
action in
}))
self.presentViewController(alert, animated: true, completion: nil)
}
return cell
}
It looks like your cellForRowAtIndexPath method uses userButtonState[indexPath.row] to determine whether to change the image for the boostButton. So, you just need to amend your pressedButtonInCell method to set userButtonState for the relevant row:
func pressedButtonInCell(cell:BoostCell)
{
cell.boostButton.setImage(self.boostedButton, forState: UIControlState.Normal)
if let indexPath = self.tableView.indexPathForCell(cell) {
userButtonState[indexPath.row] = true
}
}
(This assumes self.tableView points to your table view; if not amend to provide the correct reference). Now, when you press the button on a row, the relevant element of the array will be set to true. If the row then scrolls off screen, when it scrolls back on again, the tableView:cellForRowAtIndexPath: method will check userButtonState, find it to be true and so update the image for the button. Conversely, if userButtonState is false, the image is reset (just in case the re-used cell happened to have the boostedButton image).