I want that only four specific cells can be selected at one time. When a button is pressed, I want that the selectable cells are 4 indexPath.row lower.
Example: In the beginning, indexPath.row 44-47 is selectable. If the button is pressed I want, that the indexPath.row 40-43 is selectable and so on.
I thought about making an array with the indexPath and If the button is pressed, the numbers in the array are 4 numbers lower.
Than I don't know, how to add this to the shouldSelectItemAt indexPath function.
How can I realize this?
You can use an IndexSet.
var allowedSelectionRow: IndexSet
allowedSelectionRow.insert(integersIn: 44...47) //Initial allowed selection rows
In collectionView(_:shouldSelectItemAt:)
return allowedSelectionRow.contains(indexPath.row) //or indexPath.item
Whenever you need:
allowedSelectionRow.remove(integersIn: 44...47) //Remove indices from 44 to 47
allowedSelectionRow.insert(integersIn: 40...43) //Add indices from 40 to 43
Advantage from an Array: Like a set, there is unicity of the values (no duplicates). Contains only integers, and you can add in "range" which can be useful (not add all the indices, but a range).
After comments, if you have only 4 rows allowed and consecutive, you can have that method:
func updateAllowedSectionSet(lowerBound: Int) {
let newRange = lowerBound...(lowerBound+3)
allowedSectionRow.removeAll() //Call remove(integersIn:) in case for instance that you want always the 1 row to be selectable for instance
allowedSectionRow.insert(integersIn: newRange)
}
For the first one, you just need to do:
updateAllowedSectionSet(lowerBound: 44) instead of allowedSelectionRow.insert(integersIn: 44...47)
Let's consider that the items form a String array, and you are keeping track of the selected indices as a Range.
var selectedRange: Range<Int>? {
didSet {
collectionView.reloadData()
}
}
var items: [String] = [] {
didSet {
// To make sure that the selected indices are reset everytime this array is modified,
// so as to make sure that nothing else breaks
if items.count >= 4 {
// Select the last 4 items by default
selectedRange = (items.count - 4)..<items.count
} else if !items.isEmpty {
selectedRange = 0..<items.count
} else {
selectedRange = nil
}
}
}
Then, when you are pressing the button to decrement the range, you can use this logic to handle the same:
func decrementRange() {
if var startIndex = selectedRange?.startIndex,
var endIndex = selectedRange?.endIndex {
startIndex = max((startIndex - 4), 0)
endIndex = min(max((startIndex + 4), (endIndex - 4)), items.count)
selectedRange = startIndex..<endIndex
}
}
Then, you can identify whether the the selection is being done on the active range using:
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
if let selectedRange = selectedRange {
return selectedRange.contains(indexPath.item)
}
return false
}
Note: I would advice you to verify whether this covers all the corner cases before trying it out for production code.
Related
Im trying to dynamically arranging table view when user select "type 3". It works when user select "type 3", "type 3-1" would be added in the tableview. However the program crashed when user select other than type3-1. I dont know how can I execute the "rows.remove(at:2)" before the override function is called. Any suggestion would appreciate!
class GuestViewController: UITableViewController {
var rows:[[[String:Any]]] = [[["type":RowType.DetailContent,
"subType":DCType.DCRightContent,
"name":CPFFields.CID,
"content":"9637"],
["type":RowType.DetailContent,
"subType":DCType.DCInput,
"name":CPFFields.VISIA]],
[["type":RowType.DetailTextView,
"CPFType":CPFFields.UV,
"title":CPFFields.preferenceTitle]],
[["type":RowType.DetailContent,
"subType":DCType.DCSelection,
"name":CPFFields.Phototherapy,
"title":CPFFields.anestheticTitle],
["type":RowType.DetailTextView,
"CPFType":CPFFields.Phototherapy,
"title":CPFFields.preferenceTitle]],
]
var isNewGuestSelected : Bool = false
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rows[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = rows[indexPath.section][indexPath.row]
let type = item["type"] as! RowType
if type == RowType.DetailContent
{
let cell = tableView.dequeueReusableCell(withIdentifier: "DetailNameCell", for: indexPath) as! DetailContentCell
let cpfType = item["name"] as? CPFFields ?? .Customer
cell.name.text = CPFFields.localizedString(from: cpfType)
if let field = item["title"] as? CPFFields
{
cell.name.text = CPFFields.localizedString(from: field)
}
cell.moreSlectionLeftSpace = true
var content:String? = ""
cell.type = cpfType
switch cpfType {
case .CID:
content = (profile?.birthDate.dateFromDateString?.stringForPaitentId ?? "") + (profile?.name ?? "")
case .CT:
content = ""
if let profile = profile
{
content = CPFCustomerType.localizedString(from: profile.type)
//New Guest
if(content == CPFCustomerType.type1.rawValue){
rows[0].insert(["type":RowType.DetailContent,
"subType":DCType.DCRightContent,
"name":CPFFields.CID,
"content":"9637"], at: 1)
isNewGuestSelected = true
} else{
if isNewGuestSelected == true{
rows[0].remove(at: 1)
isNewGuestSelected = false
}
}
}
let subType = item["subType"] as! DCType
cell.setcontentType(type: subType, content: content)
return cell
}
I expected not to see "rows[0][2]" after running "rows[0].remove(at:1)".
However the log is printing
rows[0][0]
rows[0][1]
rows[0][2]
then
it crashed at "let item = rows[indexPath.section][indexPath.row]"
because it is out of range
You are modifying your content while rendering, thus after numberOfRows:inSection: was called. Therefore the tableView is trying to access an element that no longer exists, since you removed it.
Your cycle:
→ number of rows 4
→ removed item, contents now has 3 items
→ cell for item 0
→ cell for item 1
→ cell for item 2
- cell for item 3 → crash
Consider replacing the logic you have here outside of the cellForRow method, and doing these operations before you reload your tableView.
You should use the tableView:cellForRow:atIndexPath strictly for dequeueing your cells and configuring them; not for modifying the underlying data store since funky things like you're experiencing now can happen.
If you provide a bit more context I can probably tell you where to place your code to fix this issue.
Actually, the solution is quite simple. I just added tableView.reloadData() after removing the array, and the UI can then be updated.
if isNewGuestSelected == true{
rows[0].remove(at: 1)
isNewGuestSelected = false
tableView.reloadData()
}
I am writing a NSTableView based view. And I try to use:
let selectedRowNumber = tableViewMesos.selectedRow and
let selectedColumnNumber = tableViewMesos.selectedColumn to identify the textField that is in the cell View that I want to save into my array:
taulaDeConsumsMesos[selectedColumnNumber].filesConsum![selectedRowNumber] = sender.doubleValue
But a problem happens here, because the return value of selectedColumnNumber is -1 which is the value of non-selected Column for any row selected of any column.
But the selectedRowNumberreturns the index of the row properly.
So the program crashes when finishing the edition of a textfield in a cell. Because the selectedColumnNumber is -1 out of the limits of the array. See the code below to identify where this happens exactly.
Could you help me? I lost too many hours on this..
I'm new on OSX programing I hope you could help me.
The app layout
Here is the completed code:
class BalancGlobalViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate{
#IBOutlet weak var tableViewMesos: NSTableView!
let mesos = ColumnaDeConsumsMes() // contains an array of doubles called .filesConsum that represent the rows that I want to show
let consum = ColumnaDeConsumsMes()
var taulaDeConsumsMesos = [ColumnaDeConsumsMes]() // array of arrays that represent the columns with rows in it that will be the tableView data
func setUpMesos(){ // init table columns
mesos.filesConsum = [1, 2, 3, 4, 5, 6, 7, 8 , 9, 10, 11, 12, nil]
consum.filesConsum = [100, 100, 100, nil, nil, nil, nil, nil , nil, nil, nil, nil, nil]
taulaDeConsumsMesos.append(mesos)
taulaDeConsumsMesos.append(consum)
}
func setUpGradient(){
...not important here
}
override func viewDidLoad() {
super.viewDidLoad()
setUpMesos()
tableViewMesos.setDelegate(self)
tableViewMesos.setDataSource(self)
setUpGradient()
}
override func viewDidLayout() {
super.viewDidLayout()
setUpGradient()
}
#IBAction func alEditarUnTextFieldDeConsum(sender: NSTextField) { // this is the IBAction of the TextField of the Consum Column (see the image)
let selectedRowNumber = tableViewMesos.selectedRow
let selectedColumnNumber = tableViewMesos.selectedColumn
print("SelectedRowNumber! = \(selectedRowNumber)")
print("SelectedColumnNumber! = \(selectedColumnNumber)")
// the selectedColumnNumber return always -1 except when clicking the header of the column, then return 0 or 1 depending on the column. But it should return 0 even when the user edit a row of the index 0 column !!!
let numero = tableViewMesos.numberOfColumns
print("numero de columnes: \(numero)") // returns 2
if selectedRowNumber != -1 { //-1 is returned when no row is selected in the TableView
taulaDeConsumsMesos[selectedColumnNumber].filesConsum![selectedRowNumber] = sender.doubleValue //try to save here the value edited by the user on my array of arrays of doubles
//here the program crashes! selectedColumnNumber = -1
print("Sender! = \(sender.doubleValue)") // the sender returns the information correctly
//print("SelectedRowNumber! = \(selectedRowNumber)") // this works too!
reloadTaula()
}
}
func tableViewSelectionDidChange(notification: NSNotification) {
print("columna: \(tableViewMesos.selectedColumn)")
print("row: \(tableViewMesos.selectedRow)")
print("tag: \(tableViewMesos.selectedTag())")
}
// the tableViewMesos.selectedColumn return always -1 except when clicking the header of the colum, then return 0 or 1 depending on the column. But i should return 0 even when I edit a row of the index 0 column !!!
func reloadTaula() {
tableViewMesos.reloadData()
}
// MARK: - Datasource
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
if (tableView.identifier == "TaulaMesos") {
return 13
}
}
// MARK: - Delegate
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
var valorCela: Double
var cellIdentifier:String=""
if tableColumn == tableView.tableColumns[0] {
if taulaDeConsumsMesos[0].filesConsum![row] == nil {
return nil
}else {
valorCela = taulaDeConsumsMesos[0].filesConsum![row]!
cellIdentifier = "MesCellID"
}
} else {
if taulaDeConsumsMesos[1].filesConsum![row] == nil {
return nil
}else {
valorCela = taulaDeConsumsMesos[1].filesConsum![row]!
cellIdentifier = "ConsumCellID"
}
}
if let cell = tableView.makeViewWithIdentifier(cellIdentifier, owner: nil ) as? NSTableCellView {
cell.textField?.doubleValue = valorCela
return cell
}
return nil
}
}
Table views do not support selecting individual cells. Both styles of table view support selecting rows. Old-style NSCell-based table views support selecting columns, but modern view-based table views no longer support selecting columns. But it would never have been the case that both selectedRow and selectedColumn were valid at the same time.
From the 10.7 AppKit release notes, where view-based table views were introduced:
Note that the View Based TableView does not support column selection.
To get the row and column of the cell whose text field sent the action, you should use tableViewMesos.rowForView(sender) and tableViewMesos.columnForView(sender). Note, especially, that there's nothing guaranteeing that the text field that's sending the action method is in a selected row. So, don't think of it as selected. It's just the one that's been manipulated by the user.
I've already searched this before asking the question but I didn't find what I need.
I'm building this app where the user puts a task (not going to the app store, just for me and some friends), and the task has a category. For example: school, home, friends, etc. When the user is going to add a new task, there are 2 text fields, the description text field and the category text field. I'm using a UIPickerView so the user picks a category, then, after creating the new task, it will add the category to an array I've created called "categories".
I want to put an UISegmentedControl on top of the table view with the sections:
All - School - Home - Friends
If all is selected, it will show all the cells with no filtering. If not, it will show the cell(s) with the corresponding categories.
I've read that I need to create table view sections to each category, but this would change my code a lot, and I don't even have an idea of how to work with multiple table view sections, I've tried once but it kept repeating the cells of one section in the second.
So how can I filter the cells per category?
Can I just put for example this? :
if //code to check in which section the picker is here {
if let schoolCell = cell.categories[indexPath.row] == "School" {
schoolCell.hidden = true
}
}
Please help me!!!
EDIT:
I have this code by now:
if filterSegmentedControl.selectedSegmentIndex == 1 {
if categories[indexPath.row] == "School" {
}
}
I just don't know where to go from here. How do I recognize and hide the cells?
It seems to me that you may want to take a simpler approach first and get something working. Set up your ViewController and add a tableView and two(2) arrays for your table data. One would be for home and the other for work. Yes, I know this is simple but if you get it working, then you can build on it.
Add a variable to track which data you are displaying.
#IBOutlet var segmentedControl: UISegmentedControl!
#IBOutlet var tableView: UITableView!
// You would set this to 0, 1 or 2 for home, work and all.
var dataFilter = 0
// Data for work tasks
var tableDataWork : [String] = ["Proposal", "Send mail", "Fix printer", "Send payroll", "Pay rent"]
// Data for home tasks
var tableDataHome : [String] = ["Car payment", "Mow lawn", "Carpet clean"]
Add these functions for the segmented control.
#IBAction func segmentedControlAction(sender: AnyObject) {
switch segmentedControl.selectedSegmentIndex {
case 0:
print("Home")
dataFilter = 0
case 1:
print("Work")
dataFilter = 1
case 2:
print("All")
dataFilter = 2
default:
print("All")
dataFilter = 2
}
reload()
}
func reload() {
dispatch_async( dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("task-cell")
var title: String?
switch dataFilter {
case 0:
title = tableDataHome[indexPath.row]
case 1:
title = tableDataWork[indexPath.row]
case 2:
if indexPath.row < tableDataWork.count {
title = tableDataWork[indexPath.row]
} else {
title = tableDataHome[indexPath.row - tableDataWork.count]
}
default:
if indexPath.row < tableDataWork.count {
title = tableDataWork[indexPath.row]
} else {
title = tableDataHome[indexPath.row + tableDataWork.count]
}
}
cell?.textLabel?.text = title
if cell != nil {
return cell!
}
return UITableViewCell()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// If
switch dataFilter {
case 0: return tableDataHome.count
case 1: return tableDataWork.count
default: return tableDataHome.count + tableDataWork.count
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1;
}
You can find the entire project here: https://github.com/ryantxr/segmented-control-app
It depends on your tableview.
If you use NSFetchedResultsController then you need to modify your fetch request. If you use an array directly, just use the filter function in Swift, passing in the condition, e.g. filteredArray = array.filter{$0.isAudioFile} Then, after setting your datasource array to the filtered one, call reloadData on your tableview.
You will need to keep a reference to the full array, and use the filtered one as your datasource in cellForRow...
i have use long Press TapGesture on UITableView then i have create one variable indexPath The Problem is how to convert indexpath value in integer because i have use this value in UITableView index path i cant able to use this value as a integer
Here is my code
func cellLongPressed(recognizer: UILongPressGestureRecognizer) {
let point = recognizer.locationInView(tbl_LLTrandingJive)
let indexPath = tbl_LLTrandingJive.indexPathForRowAtPoint(point)
if indexPath == nil {
} else if recognizer.state == UIGestureRecognizerState.Began {
var getin:int = (indexPath?.row)?.toIntMax()
println("LONG indexPath_localTable:\(indexPath?.row)")
myarray.ovjecatindex(0)
}
to get the number of row you need
indexPath.row //current row number in section
if you have sections than
indexPath.section //current section number
You can't convert it to int directly because it's complex object.
I have a NSTableView with one column. I would like to print the row number of the row that the user has clicked on. I am not sure where I should start with this. Is there a method for this?
You can use the selectedRowIndexes property from the tableView in the tableViewSelectionDidChange method in your NSTableView delegate.
In this example, the tableView allows multiple selection.
Swift 3
func tableViewSelectionDidChange(_ notification: Notification) {
if let myTable = notification.object as? NSTableView {
// we create an [Int] array from the index set
let selected = myTable.selectedRowIndexes.map { Int($0) }
print(selected)
}
}
Swift 2
func tableViewSelectionDidChange(notification: NSNotification) {
var mySelectedRows = [Int]()
let myTableViewFromNotification = notification.object as! NSTableView
let indexes = myTableViewFromNotification.selectedRowIndexes
// we iterate over the indexes using `.indexGreaterThanIndex`
var index = indexes.firstIndex
while index != NSNotFound {
mySelectedRows.append(index)
index = indexes.indexGreaterThanIndex(index)
}
print(mySelectedRows)
}
Use -selectedRowIndexes
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSTableView_Class/#//apple_ref/occ/instp/NSTableView/selectedRowIndexes
Then you can use those indexes to grab the data from your dataSource
(typically an array)