How can i create a hamburger menu with Sub Menu inside - ios

I am working on an app in which requirement is to create a hamburger menu with submenu in it like
I tried different method using table inside table view cell etc but unable to create this menu.
if someone has a solution then recommend me

You can create such an item structure as your data source
struct Item {
let text: String
var subItems: [String]?
var isExpanded = false
init(_ text: String, items: [String]? = nil) {
self.text = text
self.subItems = items
}
}
Usage
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView: UITableView!
private let imgOpen = UIImage(named: "open")
private let imgClose = UIImage(named: "close")
private var dataSource = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "groupCell")
self.tableView.dataSource = self
self.tableView.delegate = self
self.dataSource.append(Item("HOME"))
self.dataSource.append(Item("ABOUT US"))
self.dataSource.append(Item("OUR PROJECTS", items: ["Project-1", "Project-2", "..."]))
self.dataSource.append(Item("BAHRIA TOWN PHASE 1 - 7"))
self.dataSource.append(Item("BAHRIA TOWN PHASE 8"))
self.tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return self.dataSource.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let item = self.dataSource[section]
if item.isExpanded, let count = item.subItems?.count {
return count + 1
}
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = self.dataSource[indexPath.section]
let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath)
var imageView: UIImageView?
if indexPath.row > 0, let text = item.subItems?[indexPath.row - 1] {
cell.textLabel?.text = text
} else {
cell.textLabel?.text = item.text
if item.subItems != nil {
imageView = UIImageView(image: item.isExpanded ? self.imgClose : self.imgOpen)
}
}
cell.accessoryView = imageView
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = self.dataSource[indexPath.section]
if indexPath.row == 0 && item.subItems != nil {
self.dataSource[indexPath.section].isExpanded = !item.isExpanded
let indexSet = IndexSet(integer: indexPath.section)
tableView.reloadSections(indexSet, with: .automatic)
} else {
// non-expandable menu item tapped
}
}
}

You should separate the process.
First, create the hamburger menu: for this, I recommend using this 3rd party library: https://github.com/John-Lluch/SWRevealViewController
With the help of this, it is very easy to create a side out menu from the left side of the screen.
The best thing that you will get a ViewController which will responsible for the menu, so you can easily customize it.
Second, as mentioned below, you should use a tableView with expandable cells. The best way to do this is to basically just show the headers of the cells. If the user taps on a header, then show the actual cell. (rowheight > 0). There is a tutorial about this: https://www.youtube.com/watch?v=bSKUYRsMCrM

-> you can create a sliding drawer menu(hamburger menu) using any of the following libraries:
1) REFrostedViewController
2) SWRevealViewController or any other
-> Sub Menu: In the drawer view controller, you have to add a table view and implement expandable/collapsible sections to display a submenu. You can follow any tutorial explaining about expand-collapse table view sections. Some of the tutorial links are below:
https://github.com/jeantimex/ios-swift-collapsible-table-section
https://medium.com/#legonaftik/uitableview-with-collapsible-sections-927d726b985c

1st Follow https://github.com/jonkykong/SideMenu.
And then to make EXPANDABLE Cells:-
You just need to create 2 Cells in UITableView(In Storyboard). First cell for those who are not expandable and Second cell for the expandable.
class SideMenuTableViewController: UITableViewController {
// MARK:- Constants And Vars
var isOurProjectCellExpanded = false
}
class SideMenuTableViewController: UITableViewDataSource, UITableViewDelegate {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "simpleCell", for: indexPath) as! SideMenuBasicTableViewCell
switch indexPath.row {
case 0:
cell.itemName.text = "HOME"
break
case 1:
cell.itemName.text = "About Us"
break
case 2:
if(isOurProjectCellExpanded){
//expandedCell
let cell = tableView.dequeueReusableCell(withIdentifier: "expandedCell", for: indexPath) as! SideMenuBasicTableViewCell
cell.itemName.text = "Our Projects"
return cell
}else{
cell.arrowDownImageView.isHidden = false
cell.itemName.text = "Our Projects"
}
break
case 3:
cell.itemName.text = "Bahria Town phase 1-7"
break
case 4:
cell.itemName.text = "Bahria Town phase 8"
break
default:
break
}
return cell
}
//And in `DidSelectRow` Method
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if(indexPath.row == 2){
if(isOurProjectCellExpanded){
isOurProjectCellExpanded = false
tableView.reloadRows(at: [indexPath], with: .none)
}else{
isOurProjectCellExpanded = true
tableView.reloadRows(at: [indexPath], with: .none)
}
}else if(indexPath.row == 0){
// Handle it yourself
}else if(indexPath.row == 1){
// Handle it yourself
}else if(indexPath.row == 3){
// Handle it yourself
}else if(indexPath.row == 4){
// Handle it yourself
}
}
}

Related

Cell touch action is not responding on short touches in TableView

I have a Scene in storyboard that contains HistoryTable with historyCell and own UserHistoryViewCell view. This cell and also table is set to User interaction Enabled in Traits and Interaction sections. Scrolling is disabled and Delay Touch Down is also disabled. This cell is not responding at didSelectRowAt func at short click (only when pressing longer than 5? secs). I have a Tap Gesture Recognizer in my Scene and also another table with same problem. I tried to disable Tap Gesture Recognizer, but it seems that it doesn't do anything special. I'm not sure where is problem, I have my tables delegated and using the code bellow. I also provide screenshots of Attributes inspector for the table and cell. Btw. My second table has sections, so its little bit messy.
Second Btw. I have delay touch down enabled at the screenshot, because it has no effect at all. Third Btw. I have historyButton in my historyTable which is for removing old records. It has no impact at the cell itself (I think) because same stuff happens to my second table without this button. Also it worked before I added sections to my second table.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var counter = 0
if tableView.tag == 1 {
counter = (UserDefaults.standard.stringArray(forKey: "HistoryArray") ?? [String]()).count
let base = 60
self.historyTableHeightConstraint.constant = CGFloat(base) + CGFloat(counter) * 40
} else {
switch section {
case 0:
counter = presenter.storelist?.count ?? 0
case 1:
counter = presenter.storelist?.count ?? 0
default:
counter = 0
notFoundLabel.isHidden = false
}
}
return counter
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tableView.tag == 1 {
let defaults = UserDefaults.standard
let userSearchHistory = defaults.stringArray(forKey: "HistoryArray") ?? [String]()
let cell = tableView.dequeueReusableCell(withIdentifier: "historyCell", for: indexPath) as! UserHistoryTableViewCell
cell.titleLabel.text = userSearchHistory.reversed()[indexPath.row]
cell.historyButton.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
cell.historyButton.tag = indexPath.row
if self.editable == true {
let newImage = UIImage(named: "RemoveIcon")
cell.historyButton.setImage(newImage, for: .normal)
cell.historyButton.isEnabled = true
} else {
let newImage = UIImage(named: "searchHistoryIcon")
cell.historyButton.setImage(newImage, for: .normal)
cell.historyButton.isEnabled = false
}
return cell
} else {
switch indexPath.section {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: "shopSearchCell", for: indexPath) as! ShopSearchTableViewCell
cell.shopLabel.text = presenter.storelist?[indexPath.row].storename
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: "SearchResultCell", for: indexPath) as! SearchResultCell
cell.SearchResultLabel.text = presenter.storelist?[indexPath.row].storename
return cell
default:
return UITableViewCell()
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(#function + "\(indexPath.row)")
}
func tableView(_ tableView: UITableView, titleForHeaderInSection
section: Int) -> String? {
if tableView.tag == 2 {
if section == 0 {
if self.tableView(tableView, numberOfRowsInSection: section) > 0 {
return "Search shop"
} else {
return nil
}
} else {
if self.tableView(tableView, numberOfRowsInSection: section) > 0 {
return "Search"
} else {
return nil
}
}
} else {
return nil
}
}
// Sectionæ•°
func numberOfSections(in tableView: UITableView) -> Int {
if tableView.tag == 2 {
return 2
}
return 1
}

Single and MultiSelection cells in same tableView | Swift

Before duplicating this question, please be known that I've spent days on this issue, working hours, and looking for all same sort of questions on SO, but there is something I am missing or doing wrong.
I have a tableView in which the data is being populated via API response. Below is the model I have.
struct Model : Codable {
let bugClassification : [Bug]?
}
struct Bug : Codable {
let selectable : String? //Telling wether cell is single/Multi selected
var options : [Options]?
}
struct Options : Codable, Equatable {
let title : String?
let id: Int
var isCellSelected: Bool = false
}
Scenario
I want to create multiple sections, each having different cell depending upon the type of selectable, either single or multi. I have achieved that, but the problem I am getting is that whenever I scroll, random cells are also selected. Now, I know this behaviour is because of tableView reusing the cells. But I am confused as how to handle all this. Also, I want to put the validation on the sections, that is, every section should have atleast one cell selected. Kindly guide me in the right direction, and any small help would be appreciated. Below is my code.
CellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if bugClassification[indexPath.section].selectable?.lowercased() == "multi-select" {
//Multi-Selection
let cell = tableView.dequeueReusableCell(withIdentifier: multiSelectionCellID) as! MultiSelectionCell
let item = bugClassification[indexPath.section].options![indexPath.row]
cell.label.text = item.title
if item.isCellSelected {
cell.checkMarkImageView.alpha = 1
cell.checkMarkView.layer.borderColor = UIColor.white.cgColor
cell.checkMarkView.backgroundColor = .emerald
} else if item.isCellSelected {
cell.checkMarkImageView.alpha = 0
cell.checkMarkView.layer.borderColor = UIColor.veryLightBlue.cgColor
cell.checkMarkView.backgroundColor = .white
}
return cell
} else {
//Single-Selection
let cell = tableView.dequeueReusableCell(withIdentifier: singleSelectionCellID) as! SingleSelectionCell
let item = bugClassification[indexPath.section].options![indexPath.row]
cell.label.text = item.title
if item.isCellSelected {
cell.checkMarkImageView.alpha = 1
cell.checkMarkView.layer.borderColor = UIColor.emerald.cgColor
} else {
cell.checkMarkImageView.alpha = 0
cell.checkMarkView.layer.borderColor = UIColor.veryLightBlue.cgColor
}
return cell
}
}
DidSelectRow Method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if bugClassification[indexPath.section].selectable?.lowercased() == "multi-select" {
var item = bugClassification[indexPath.section].options![indexPath.row]
item.isCellSelected = !item.isCellSelected
bugClassification[indexPath.section].options![indexPath.row] = item
self.tableView.reloadRows(at: [indexPath], with: .automatic)
} else {
let items = bugClassification[indexPath.section].options
if let selectedItemIndex = items!.indices.first(where: { items![$0].isCellSelected }) {
bugClassification[indexPath.section].options![selectedItemIndex].isCellSelected = false
if selectedItemIndex != indexPath.row {
bugClassification[indexPath.section].options![indexPath.row].isCellSelected = true
}
} else {
bugClassification[indexPath.section].options![indexPath.row].isCellSelected = true
}
self.tableView.reloadSections([indexPath.section], with: .automatic)
}
}
In cellForRowAt
if item.isCellSelected == true{
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
and update the model by every selection
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let item = bugClassification[indexPath.section].options![indexPath.row]
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .none
if indexPath.section == 0{
item.isCellSelected.isSelected = false
}else{
item.isCellSelected.isSelected = false
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = bugClassification[indexPath.section].options![indexPath.row]
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
if indexPath.section == 0{
item.isCellSelected.isSelected = true
}else{
item.isCellSelected.isSelected = true
}
}
}

How to remove row in UITableViewController presented as popover

I have a custom UITableViewController that I present as a popover in my app. In some of the cells there is a delete button (trash can) to remove that item. Everything works as it should except that I the UI is not update when pressing the delete button. That is, the data is cleared and I call self.tableView.reloadData(), but the cell remains visible in the UI. (Pressing the delete button again makes the app crash in my C++ code because of an assert). I have no storyboard or xib as I do not need it. I only want this to be in code.
What am I missing? It might be something simple, but I cannot fathom why. I have tried:
Separate data source implementation.
Calling reloadData() both sync and async.
Setting delegate to self.
Various other hacks.
Here is the UITableViewController implementation:
import Foundation
class IngredientInfoPopoverViewController : UITableViewController
{
var slViewController: ShoppingListViewController?;
var ingredientName: String = "Ingrediens";
#IBOutlet var uniqueIngredients: [Ingredient] = []; // Unique per *recipe* so that we can list all the recipes for the ingredients
var clickedCellIndexPath: IndexPath? = nil;
enum SECTIONS : Int
{
case HEADER = 0;
case RECIPE = 1;
}
static let ROW_HEIGHT = 44;
override func viewDidLoad()
{
super.viewDidLoad();
tableView.register(UINib(nibName: "OpenIngredientInfoCell", bundle: nil), forCellReuseIdentifier: "OpenIngredientInfoCell");
tableView.register(UINib(nibName: "OpenRecipeCell", bundle: nil), forCellReuseIdentifier: "OpenRecipeCell");
tableView.separatorStyle = .singleLine;
tableView.bounces = false; // "Static" table view
updateSize();
}
func updateSize()
{
let totalCount = min(uniqueIngredients.count + 1, 6); // + 1: header row. min: Allow max 5 recipes in list (enables scrolling)
self.preferredContentSize = CGSize(width: 300, height: totalCount * IngredientInfoPopoverViewController.ROW_HEIGHT);
}
func setup(slvc: ShoppingListViewController?, ingredients: [Ingredient], clickedCellIndexPath: IndexPath)
{
self.slViewController = slvc;
self.clickedCellIndexPath = clickedCellIndexPath;
if (ingredients.count > 0)
{
let first = ingredients[0];
for i in ingredients
{
assert(i.getId() == first.getId());
}
ingredientName = first.getName();
var uniqueRecipeNames: Set<String> = [];
for i in ingredients
{
uniqueRecipeNames.insert(i.getRecipeName());
}
let sorted = uniqueRecipeNames.sorted();
uniqueIngredients.removeAll();
for s in sorted
{
for i in ingredients
{
if (i.getRecipeName() == s)
{
uniqueIngredients.append(i);
break;
}
}
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int
{
return 2;
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
switch section
{
case SECTIONS.HEADER.rawValue:
return 1;
case SECTIONS.RECIPE.rawValue:
return uniqueIngredients.count;
default:
assert(false);
return 0;
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
switch (indexPath.section)
{
case SECTIONS.HEADER.rawValue:
assert(indexPath.row == 0);
if (uniqueIngredients.count > 0)
{
let ingredient = uniqueIngredients[0]; // All are the same ingredient
self.dismiss(animated: true, completion: nil);
slViewController?.onIngredientInfoButtonClicked(ingredient);
}
break;
case SECTIONS.RECIPE.rawValue:
if (indexPath.row < uniqueIngredients.count)
{
let ingredient = uniqueIngredients[indexPath.row];
self.dismiss(animated: true, completion: nil);
slViewController?.onRecipeInfoButtonClicked(ingredient);
}
break;
default:
break;
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell();
switch (indexPath.section)
{
case SECTIONS.HEADER.rawValue:
if (uniqueIngredients.count > 0)
{
let ingredient = uniqueIngredients[0];
let cell = tableView.dequeueReusableCell(withIdentifier: "OpenIngredientInfoCell", for: indexPath) as! OpenIngredientInfoCell;
cell.setup(ingredient);
}
break;
case SECTIONS.RECIPE.rawValue:
if (indexPath.row < uniqueIngredients.count)
{
cell.selectionStyle = .none; // Without this the cell contents become gray and disappear when long pressing! FML
let ingredient = uniqueIngredients[indexPath.row];
let cell = tableView.dequeueReusableCell(withIdentifier: "OpenRecipeCell", for: indexPath) as! OpenRecipeCell;
cell.setup(self, ingredient, clickedCellIndexPath);
}
break;
default:
break;
}
return cell;
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
return CGFloat(IngredientInfoPopoverViewController.ROW_HEIGHT);
}
func ingredientRemoved(_ ingredient: Ingredient)
{
for i in 0..<uniqueIngredients.count
{
if (uniqueIngredients[i].getRecipeId() == ingredient.getRecipeId())
{
uniqueIngredients.remove(at: i);
// let indexPath = IndexPath(row: i, section: SECTIONS.RECIPE.rawValue);
// self.tableView.deleteRows(at: [indexPath], with: .fade);
DispatchQueue.main.async {
self.tableView.reloadData();
}
break;
}
}
if (uniqueIngredients.count == 0)
{
self.dismiss(animated: true, completion: nil);
}
else
{
DispatchQueue.main.async {
self.tableView.reloadData();
}
}
}
}
Here is how I present the IngredientInfoPopoverViewController:
#objc func ingredientInfoClicked(_ sender: UITapGestureRecognizer)
{
let tapLocation = sender.location(in: self.tableView)
let indexPath = self.tableView.indexPathForRow(at: tapLocation)!
let ingredients = CppInterface.shoppingList.getIngredients(UInt(indexPath.section), position: UInt(indexPath.row));
let controller = IngredientInfoPopoverViewController();
controller.setup(slvc: self, ingredients: ingredients!, clickedCellIndexPath: indexPath);
controller.modalPresentationStyle = .popover;
controller.popoverPresentationController!.delegate = self;
self.present(controller, animated: true, completion: {
self.tableView.reloadData();
});
}
Here is how the view controller looks when presented. If I click the trash can on one of the items, the data is cleared, but the cell is not removed from the UI, which is what I am trying to achieve.
I'm actually surprised your tableView shows any data at all. Because you declare cell as a let in cellForRowAt when you do let cell = UITableViewCell();, that makes it immutable, and the first cell (outside of the switch) is the one that should technically get returned. Hence why no data should be displaying. And probably also the reason why your tableView is not updating correctly.
Anyway, you should only declare cell when you're dequeueing it, and you should as much as possible, avoid force-unwrapping of a variable.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == SECTIONS.HEADER.rawValue, let cell = tableView.dequeueReusableCell(withIdentifier: "OpenIngredientInfoCell", for: indexPath) as? OpenIngredientInfoCell {
// not sure this check is necessary, but I'm adding it because it was in your original code
guard uniqueIngredients.count > 0 else { return UITableViewCell() }
let ingredient = uniqueIngredients[0]
cell.setup(ingredient)
return cell
} else if indexPath.section == SECTIONS.RECIPE.rawValue, let cell = tableView.dequeueReusableCell(withIdentifier: "OpenRecipeCell", for: indexPath) as? OpenRecipeCell {
// it shouldn't be possible for the indexPath to ever be greater than the dataSource items count, but I'll keep the check
guard indexPath.row < uniqueIngredients.count else { return UITableViewCell() }
cell.selectionStyle = .none
let ingredient = uniqueIngredients[indexPath.row]
cell.setup(self, ingredient, clickedCellIndexPath)
return cell
}
return UITableViewCell()
}
I've removed the semi-colons as they're not necessary in Swift.
For specifying the table cells' reuse identifiers, using the class names would probably be better. So you would use "\(OpenRecipeCell.self)" instead of "OpenRecipeCell"
If you are using the defaulted way of editing a UITableView (either swiping or entering edit mode), then here's my delegate code that works fine:
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedStep = appState.recipe.steps[sourceIndexPath.row]
appState.recipe.steps.remove(at: sourceIndexPath.row)
appState.recipe.steps.insert(movedStep, at: destinationIndexPath.row)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
appState.recipe.steps.remove(at: indexPath.row)
tblSteps.deleteRows(at: [indexPath], with: .automatic)
}
}
Notes:
I manually place this table view in edit mode through a UIBarButtonItem, and a cell can both be moved or deleted.
My data source is in my model, at appState.recipe.steps. The structure doesn't matter, just handling the array.
I set a Notification anytime this array is changed that triggers a reloadData() in this table view.
I don't see either of these delegate methods listed, so I'm posting this answer. If by chance it doesn't help you, I'll gladly delete this.

Swift TableView insert row below button clicked

I am new to Swift and I am using Swift 4.2 . I have a TableView with a label and button . When I press a button I would like to add a new row directly below the row in which the button was clicked . Right now when I click a button the new row gets added to the bottom of the TableView every time. I have been looking at posts on here but haven't been able to get it working this is my code base . I have a method called RowClick I get the indexpath of the row that was clicked but do not know how to use that to get the new row to appear directly below the clicked row .
class ExpandController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var TableSource: UITableView!
var videos: [String] = ["FaceBook","Twitter","Instagram"]
override func viewDidLoad() {
super.viewDidLoad()
TableSource.delegate = self
TableSource.dataSource = self
TableSource.tableFooterView = UIView(frame: CGRect.zero)
// Do any additional setup after loading the view.
}
#IBAction func RowClick(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ExpandTVC else {
return
}
let indexPath = TableSource.indexPath(for: cell)
InsertVideoTitle(indexPath: indexPath)
}
func InsertVideoTitle(indexPath: IndexPath?)
{
videos.append("Snapchat")
let indexPath = IndexPath(row: videos.count - 1, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [indexPath], with: .automatic)
TableSource.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoTitle = videos[indexPath.row]
let cell = TableSource.dequeueReusableCell(withIdentifier: "ExpandTVC") as! ExpandTVC
cell.Title.text = videoTitle
cell.ButtonRow.tag = indexPath.row
cell.ButtonRow.setTitle("Rows",for: .normal)
return cell
}
}
This is how my table looks I clicked the Facebook Rows button and it appended the string SnapChat . The Snapchat label should appear in a row below Facebook instead . Any suggestions would be great !
I think the easiest solution without re-writing this whole thing would be adding 1 to the current row of the IndexPath you captured from the action.
let indexPath = TableSource.indexPath(for: cell)
var newIndexPath = indexPath;
newIndexPath.row += 1;
InsertVideoTitle(indexPath: newIndexPath);
I did this from memory because I am not near an IDE, so take a look at the change and apply that change if needed in any other location.
class ExpandController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var TableSource: UITableView!
var videos: [String] = ["FaceBook","Twitter","Instagram"]
override func viewDidLoad() {
super.viewDidLoad()
TableSource.delegate = self
TableSource.dataSource = self
TableSource.tableFooterView = UIView(frame: CGRect.zero)
// Do any additional setup after loading the view.
}
#IBAction func RowClick(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ExpandTVC else {
return
}
let indexPath = TableSource.indexPath(for: cell)
var newIndexPath = indexPath;
newIndexPath.row += 1;
InsertVideoTitle(indexPath: newIndexPath);
}
func InsertVideoTitle(indexPath: IndexPath?)
{
videos.append("Snapchat")
let indexPath = IndexPath(row: videos.count - 1, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [indexPath], with: .automatic)
TableSource.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoTitle = videos[indexPath.row]
let cell = TableSource.dequeueReusableCell(withIdentifier: "ExpandTVC") as! ExpandTVC
cell.Title.text = videoTitle
cell.ButtonRow.tag = indexPath.row
cell.ButtonRow.setTitle("Rows",for: .normal)
return cell
}
}
Your current code calls append to add the new item at the end of the array. What you want to do is insert a new row at indexPath.row+1. Array has an insert(element,at:) function.
You have to handle the case where the user has tapped the last row and not add 1 to avoid an array bounds error:
func InsertVideoTitle(indexPath: IndexPath)
{
let targetRow = indexPath.row < videos.endIndex ? indexPath.row+1 : indexPath.row
videos.insert("Snapchat" at:targetRow)
let newIndexPath = IndexPath(row: targetRow, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [newIndexPath], with: .automatic)
TableSource.endUpdates()
}

Getting TableViewCell Index is nil on dropdown item selection

I want tableviewcell index on dropdown item selection. But the index is nil when I am clicking on dropdown item. Is there any way to get index on dropdown item selection? If anyone have any better solution give me some idea.
let EditDropDown = DropDown()
lazy var dropDowns: [DropDown] = {
return [
self.EditDropDown
]
}()
This is my function which I am using for DropDown List.
func setupGenderDropDown() {
let cellHeader = tableview.dequeueReusableCell(withIdentifier: "CellRIDHeader") as! SPOccupationCell
EditDropDown.anchorView = cellHeader.btnDots
EditDropDown.bottomOffset = CGPoint(x: 0, y: 40)
// You can also use localizationKeysDataSource instead. Check the docs.
EditDropDown.dataSource = [
"Edit",
"Make Default",
"Delete"
]
// Action triggered on selection
EditDropDown.selectionAction = { [weak self] (index, item) in
cellHeader.btnDots.setTitle(item, for: .normal)
if item == "Edit"
{
// I am Getting Cell Index but index is nil
let cell = self!.tableview.dequeueReusableCell(withIdentifier: "CellRIDHeader") as! SPOccupationCell
let indexPath = self!.tableview.indexPath(for: cell)
print(indexPath as Any)
let occupation_id = self!.arrayOccupation[(indexPath?.row)!].occupation_Main_id
print(occupation_id)
let next = self!.storyboard?.instantiateViewController(withIdentifier: "EditOccupationVCSID") as! EditOccupationVC
self!.navigationController?.pushViewController(next, animated: false)
next.occupationId = occupation_id
}
else if item == "Make Default"
{
print("B")
}
else if item == "Delete"
{
print("c")
}
}
}
I am assuming you are using DropDown library to show dropdown. There is a problem where you are getting cell when it is tapped so I have created a demo project (simple tableView and not with custom UITableViewCell) for you and I have added comment to explain the changes. Consider below code:
import UIKit
import DropDown
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var items: [String] = ["We", "Heart", "Swift"]
let editDropDown = DropDown() //Object name should start with small letter
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
func setupGenderDropDown(cell: UITableViewCell) { //Pass your cell with argument and change type to your custom cell
//By changing cell argument with your custom cell you will get your button for anchor
editDropDown.anchorView = cell.textLabel
editDropDown.bottomOffset = CGPoint(x: 0, y: 40)
editDropDown.dataSource = [
"Edit",
"Make Default",
"Delete"
]
//Here you need to update selectionAction from their library page
editDropDown.selectionAction = { [unowned self] (index: Int, item: String) in
//Here you will get selected item and index
print("Selected item: \(item) at index: \(index)")
if item == "Edit"
{
print(item)
print(index)
}
else if item == "Make Default"
{
print("B")
}
else if item == "Delete"
{
print("c")
}
}
//This was missing in your code
editDropDown.show()
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! UITableViewCell
cell.textLabel?.text = self.items[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Get the selected cell this way
guard let indexPath = tableView.indexPathForSelectedRow else { return }
guard let currentCell = tableView.cellForRow(at: indexPath) else { return }
//Pass your selected cell to setupGenderDropDown method
setupGenderDropDown(cell: currentCell)
}
}
HERE you can check demo project. And it's created into Xcode 10.1

Resources