I have create a UITableView in IB. This view contains 5 sections and every section some cells. The first cell in some sections gives the option to the end user to show/hide the rest of the cells that belongs to the same section.
My code so far:
import UIKit
class SettingsVC: UITableViewController {
#IBOutlet var showCallForwardSwitch: UISwitch?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func toggleValueChanged(sender: UISwitch) {
if showCallForwardSwitch!.on {
println("switch is on")
} else {
println("switch is off")
}
tableView.reloadData()
}
}
So there is only an IBOutlet and an IBAction. I can get the event via the toogleValueChanged func, however I don't know what to do from now on. Which methods to I need to use?
override func tableView(tableView:UITableView, heightForRowAtIndexPath indexPath:NSIndexPath)->CGFloat
{
let cell:DetailCell = self.tableView.cellForRowAtIndexPath(indexPath) as DetailCell // ????????
var height:CGFloat = 84.0;
if ("toggel on"){
height = 84.0;
}
else{
height = 0.0;
}
return height;
}
You don't wanna reload the whole tableView because of one section. I suggest using deleteRowsAtIndexPaths(_:withRowAnimation:) keeping only the first cell so the user will be able to display the rest of the section again. Update your data source as well and use some flag so you know which cell to not display.
Related
I created a tableview with a custom cell.
Inside my UITableViewCell file I have the following code :
var myTipView:EasyTipView?
#IBAction func infoBtn_pressed(_ sender: UIButton) {
//print(diseases)
if self.myTipView == nil
{
self.myTipView = EasyTipView(text: diseases, preferences: EasyTipView.globalPreferences)
self.myTipView!.show(forView: sender)
}
else
{
self.myTipView!.dismiss()
self.myTipView = nil
}
}
and inside my UIViewController I have a tableview with the code :
var myTipView:EasyTipView?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myTable.dequeueReusableCell(withIdentifier: "diffCell") as! diffCell
cell.myTipView = self.myTipView
}
the when I tried to use the value of self.myTipView I found it nil and this is obvious because it have no value until the infoBtn_pressed is activated and in this case cell.myTipView is always nil when the table first created
how can i get self.myTipView to have the value after the button is pressed to use it inside UIViewController
Once you set the myTipView value, You need to refresh the table by doing this :-
tableView.reloadData()
You could try adding an observer to your myTipView parameter:
var myTipView : EasyTipView? {
didSet {
// Test that myTipView is non-nil
if let _ = myTipView {
// implement a function here that updates the cells in the table view...
}
}
}
I'm assuming you can handle updating the table cells (incidentally, your implementation seems to suggest that every cell has the same EasyTipView property: is that your intention?). Hope that helps.
Finally i used delegates to notify the table when a button is clicked in the table cell and worked great (^^)
I have two UITextFields on the UITableViewCell and their IBOutlets are connected in the custom UITableViewCell class called as "CustomCell.swift".
The Enter button is there on the UIView of ViewController and its IBAction is there in the UIViewController class called as "ViewController".
On click of the Enter button I want to see if the two textFields are empty. How do I do it? Please help
create a Bool variable in your class where you have the button action
var isTextFieldTextEmpty: Bool!
then in your table view dataSource method cellForRowAtIndexPath add
if myCell.myTextField.text?.isEmpty == true {
self.isTextFieldTextEmpty = true
} else {
self.isTextFieldTextEmpty = false
}
then in the IBAction of your (Enter) button add
self.myTableView.reloadData()
self.myTableView.layoutIfNeeded()
print(self.isTextFieldTextEmpty)
if all text fields in all cells of the table view have text, it will print false, else if only one text fields among all the text fields has no text, it will print true
Here is a simple solution. It will work for any number of cells.
What you need to do is iterate through the cells and figure out if the textField that particular cell is holding is empty or not. Now the question is how will you iterate through the cells, is there any delegate for that? The answer is No.
You have to manually construct the indexPaths to get the cells from the Table.
Here is a simple walk through. Your set up is quite right. You should have a tableview in your ViewController. So, the IBOutlet of the tableview should be there. I named my TableView "myTableView". And the textField's Outlet should be inside the TableViewCell which is also right. At the end the action method for the Enter button should be in the view controller.
Make sure, you properly connect all the outlets.
Here is the sample custom TableViewCell -
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var internalTextField : UITextField!
override func awakeFromNib() {
super.awakeFromNib()
}
}
And now just go to the ViewController.swift-
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
#IBOutlet weak var myTableView : UITableView!
var numberOfCells = 2 //you can update it to be any number
override func viewDidLoad() {
super.viewDidLoad()
self.myTableView.dataSource! = self //assign the delegate
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numberOfCells
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : CustomTableViewCell = tableView.dequeueReusableCellWithIdentifier("customCell", forIndexPath: indexPath) as! CustomTableViewCell
return cell;
}
#IBAction func pressedEnter(){
var row = 0
while row < numberOfCells { //iterate through the tableview's cells
let indexPath : NSIndexPath = NSIndexPath(forRow: row, inSection: 0) //Create the indexpath to get the cell
let cell : CustomTableViewCell = self.myTableView.cellForRowAtIndexPath(indexPath) as! CustomTableViewCell
if cell.internalTextField.text!.isEmpty{
print("TextField is Cell \(row) is Empty")
}
else{
print("TextField is Cell \(row) is NOT Empty")
}
row += 1
}
}
}
There are comments which explains everything. Hope this helps.
My table view allows multiple cell selection, where each cell sets itself as selected when a button inside the cell has been clicked (similar to what the gmail app does, see picture below). I am looking for a way to let the UITableViewController know that cells have been selected or deselected, in order to manually change the UINavigationItem. I was hoping there is a way to do this by using the delegate methods, but I cannot seem to find one. didSelectRowAtIndexPath is handling clicks on the cell itself, and should not affect the cell's selected state.
The most straight forward way to do this would be to create our own delegate protocol for your cell, that your UITableViewController would adopt. When you dequeue your cell, you would also set a delegate property on the cell to the UITableViewController instance. Then the cell can invoke the methods in your protocol to inform the UITableViewController of actions that are occurring and it can update other state as necessary. Here's some example code to give the idea (note that I did not run this by the compiler, so there may be typos):
protocol ArticleCellDelegate {
func articleCellDidBecomeSelected(articleCell: ArticleCell)
func articleCellDidBecomeUnselected(articleCell: ArticleCell)
}
class ArticleCell: UICollectionViewCell {
#IBAction private func select(sender: AnyObject) {
articleSelected = !articleSelected
// Other work
if articleSelected {
delegate?.articleCellDidBecomeSelected(self)
}
else {
delegate?.articleCellDidBecomeUnselected(self)
}
}
var articleSelected = false
weak var delegate: ArticleCellDelegate?
}
class ArticleTableViewController: UITableViewController, ArticleCellDelegate {
func articleCellDidBecomeSelected(articleCell: ArticleCell) {
// Update state as appropriate
}
func articleCellDidBecomeUnselected(articleCell: ArticleCell) {
// Update state as appropriate
}
// Other methods ...
override tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueCellWithIdentifier("ArticleCell", forIndexPath: indexPath) as! ArticleCell
cell.delegate = self
// Other configuration
return cell
}
}
I would have a function like 'cellButtomDidSelect' in the view controller and in 'cellForRowAtIndexPath', set target-action to the above mentioned function
I have made a table view in iOS that displays a list of buddy (friend) requests. For the buddy request cell, I have made it a prototype cell and have given it a custom class that extends from UITableViewCell. When I click the "Accept" button on the cell, I want to remove that row from the requests array I have and remove it from the table view as well.
The three options I have considered are
1) Giving the custom cell a property for row that corresponds to the row in the table, and hence, the row in the requests array. Then, when accept is called, pass that row to the delegate function and call
requests.removeAtIndex(row)
tableView.reloadData()
which updates all the custom cells' row property. This method works. However, is this a bad practice to reload the table data (it's only reloading from the stored array, not making a network request)
2) Giving the custom cell the row property, but then calling
self.requests.removeAtIndex(row)
self.requestsTableView.beginUpdates()
self.requestsTableView.deleteRowsAtIndexPaths([NSIndexPath(forRow:row, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Fade)
self.requestsTableView.endUpdates()
However, this does not update the row value in each of the cells following the deleted cell, and I would somehow either have to update them all, or call reloadData() which isn't what I want to do.
3) Instead of passing the row value, when the "Accept" button is clicked, search for the username in the buddies list, get the index of where it is found, and then delete the row in the table using that index and deleteRowsAtIndexPaths. This seems okay to do, especially since I'll never have a huge amount of buddy requests at once and searching won't require much time at all, but I figure if I had immediate access to the row value, it would make things cleaner.
Here is the code:
View Controller
class RequestsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, RequestTableViewCellDelegate
{
// Outlet to our table view
#IBOutlet weak var requestsTableView: UITableView!
let buddyRequestCellIdentifier: String = "buddyRequestCell"
// List of buddies who have sent us friend requests
var requests = [Buddy]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
self.getBuddyRequests()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: -Table View
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return requests.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: RequestTableViewCell = tableView.dequeueReusableCellWithIdentifier(buddyRequestCellIdentifier) as! RequestTableViewCell
let buddy = requests[indexPath.row]
let fullName = "\(buddy.firstName) \(buddy.lastName)"
cell.titleLabel?.text = fullName
cell.buddyUsername = buddy.username
cell.row = indexPath.row
cell.delegate = self
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let buddy = self.requests[indexPath.row]
}
func didAccpetBuddyRequest(row: Int) {
// Remove buddy at the 'row' index
// idea 1: update all cells' 'row' value
//self.requests.removeAtIndex(row)
// reloading data will reload all the cells so they will all get a new row number
//self.requestsTableView.reloadData()
// idea 2
// Using row doesn't work here becuase these values don't get changed when other cells are added/deleted
self.requests.removeAtIndex(row)
self.requestsTableView.beginUpdates()
self.requestsTableView.deleteRowsAtIndexPaths([NSIndexPath(forRow:row, inSection: 0)], withRowAnimation: UITableViewRowAnimation.Fade)
self.requestsTableView.endUpdates()
// idea 3: don't use row, but search for the index by looking for the username
}
// MARK: -API
func getBuddyRequests() {
// self.requests = array of buddy requests from API request
self.requestsTableView.reloadData()
}
}
Custom UITableViewCell and protocol for the delegate call
protocol RequestTableViewCellDelegate {
func didAccpetBuddyRequest(row: Int)
}
class RequestTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var acceptButton: UIButton!
var delegate: RequestTableViewCellDelegate?
var buddyUsername: String?
var row: Int?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func touchAccept(sender: AnyObject) {
// <code goes here to make API request to accept the buddy request>
self.delegate?.didAccpetBuddyRequest(self.row!)
}
}
Thanks for taking the time to read this, I appreciate any help/best practices that you know that could help me in this situation.
There shouldn't be a problem with giving the cell the indexPath and delegate properties, and then informing the delegate when the Accept button has been tapped. You do need to call reloadData(), though, to update the references in the cells that are affected.
If you wish to minimise the number of reloaded rows, call reloadRowsAtIndexPaths() instead, but I think that creating the loop that creates the NSIndexPath objects will slow your app down just the same.
As an alternative I can suggest you another way:
First add action method to your acceptButton in viewController. Inside that method you can get indexPath of the cell that contains button. Here is implementation
#IBAction func acceptDidTap(sender: UIButton) {
let point = tableView.convertPoint(CGPoint.zeroPoint, fromView: button)
if let indexPath = tableView.indexPathForRowAtPoint(point) {
// here you got which cell's acceptButton triggered the action
}
}
I've googled for hours and have tried a handful of tutorials, but haven't been able to get this working:
I have a TableView, and I want to make it so pressing on a cell presents a popup that has a date picker.
I have my custom viewcontroller with the date picker presenting (popping up from the bottom), but it takes up the entire screen. Thoughts? I found one mention of this exact issue while googling but the solution didn't work.
One possibility is to overlay a subview (object of UIView) (with a date picker and a done button) on top of your tableview. Then use .hidden feature of the subview to hide/show the view. The following is an example of the tableviewcontroller. When setting up the storyboard make sure that the subview has the layout constraints so the date picker is positioned properly. I used the "resolve auto layout issues" and it worked good. Unless you do special processing the subview will get positioned at the bottom of the rows. If you have a lot of rows the aubview will get clipped or hidden completely. So it is better to position the subview at the relative to the bottom of the page in your auto layout.
Here is a simple example that worked well for me. In viewDidLoad the subview is hidden. When you click on any row it will show the subview and the date picker. When you press done it will hide the subview again.
import UIKit
class TableViewController: UITableViewController {
#IBAction func doneButton(sender: UIButton) {
// process the date using datePickerOutlet properties
subView.hidden = true // hide the subview and its components
}
#IBOutlet weak var subView: UIView!
#IBOutlet weak var datePickerOutlet: UIDatePicker!
#IBAction func datePicker(sender: UIDatePicker) {
}
override func viewDidLoad() {
super.viewDidLoad()
subView.hidden = true // hide the subview and its components
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("\(indexPath.row)")
subView.hidden = false // show the subview and its components
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel!.text = "\(indexPath.row)"
return cell
}
}
Alternatively, put it in a UIAlertView(). At least it will be centered. It's going to be tough to combine a table and a picker on a small screen, like let's say a 4S.
I think this should work:
override var preferredContentSize: CGSize {
get{
// Checks if it is currently presenting
if presentingViewController != nil {
return (datePicker.sizeThatFits(presentingViewController!.view.bounds.size))
}
return super.preferredContentSize
}
set{ super.preferredContentSize = newValue }
}
This code goes under the View controller for the popup.
Basically what it does is to set the width of the popup to the minimum size required for the date picker.
Got it from the iTunes U tutorial by Paul Hegarty