Is there an elegant and short way to progrematiclly swipe between two cells (assuming we have the desired NSIndexPath of the two cells)?
I see few possibilities here, having the information you provide.
1) You can use standard UICollectionView method: - moveItemAtIndexPath:toIndexPath:, but you must update your data source first. For example, assume you already updated data source (note that this example code is useless until you figure out index changes after moving items.):
collectionView.performBatchUpdates({ () -> Void in
collectionView.moveItemAtIndexPath(firstIndexPath,toIndexPath:secondIndexPath)
//note: proper indexes might be changed after moveItem.. method. Play with it and you'll find the proper one for your item.
collectionView.moveItemAtIndexPath(secondIndexPath,toIndexPath:firstIndexPath)
}, completion: { (finish) -> Void in
})
2) You can recalculate your layout if you use custom layout
3) You can just reload collection view with reloadData or reloadItemsAtIndexPaths E.g.:
var dataSourceArray = [1,2,3,4,5]
// some event occurred -> dataSourceArray changed
dataSourceArray = [1,2,5,4,3]
// call collectionView.reloadData() or reloadItemsAtIndexPaths(_:).
If you'll use 1st or 3rd way, in both cases data source must be up to date.
Related
I am getting an exception when doing a search on multiple section. It occurs when applying a snapshot on the datasource.
Background: I have (pre-defined) sections, and each section has a collection of items. Sections won't appear in the viewController if there are no items in section. Items are added by a function of the app. Once an item is added in one of the section, datasource update is called and will show the section with the item added.
Problem: Encountering this issue when trying to search for a non-existent item twice. To reproduce, you can enter a non-existent item, then delete the search string via a backspace, then input a non-existing item again, then error will be thrown on the dataSource.apply().
Hoping someone can help. TIA!
Here is the code:
func updateData(on searchItem: String = "") {
//create a snapshot that will be used by the datasource to apply the diff changes
snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
Manager.shared.getAllSections().forEach { section in
let items = section.items
//if search string is empty, we just assign the items of the section,
//else we filter it based on the searchItem
var filteredItems = searchItem.isEmpty ? items :
items.filter { $0.itemName.lowercased().contains(searchItem.lowercased()) }
//if theres no items filtered, we ain't appending any section and items
if filteredItems.count > 0 {
snapshot.appendSections([section])
snapshot.appendItems(filteredItems)
}
}
//when calling apply, i get the exception when calling apply on dataSource
dataSource.apply(snapshot, animatingDifferences: false)
}
//Here is the updateSearchResults delegate method triggered when typing something in the search bar
func updateSearchResults(for searchController: UISearchController) {
guard let searchedItem = searchController.searchBar.text, !searchedItem.isEmpty else {
updateData()
return
}
updateData(on: searchedItem)
}
ok so I think this is some type of internal bug in diffable data source where it doesn't like when you have 0 sections in your collection view, but the workaround I figured out was to just add a dummy section and hide the section header (if you have one).
in your updateData() method you could add:
if snapshot.numberOfItems == 0 {
snapshot.appendSections([YourSection(name: "dummy")])
}
Then if you're using a section header, give that dummy section some identifiable variable that you can use to hide the header.
When dequeuing a supplementary view (header), check if the name == "dummy" then hide the header if so.
It's a hack, but it ends up looking the exact same and you don't have to deal with ugly empty sections being displayed.
My understanding is that that happens when the compositional layout is trying to resolve layout for a section that doesn't exist in the data source.
In my case I was using UICollectionViewCompositionalLayout(sectionProvider:) which was returning old sections when my data source was returning the correct ones but different.
How I fixed it was invalidating the layout: collectionView.collectionViewLayout.invalidateLayout()
Please forgive me if I don't describe this question too well, I am new to programming MacOS apps using Swift. I know the way I'm going about this is probably wrong and I just need someone to tell me the right way.
My main app screen
I have a Core Data application that stores an ordered list of entities called Items. These Items are intended to describe a single step in an activity that describes what should happen on screen. If you know the Mac application QLab each Item is like a single cue in QLab.
I have created an Activity class that is designed to read through each Item to determine the Item type and it's related information. Once the Item type has been determined the Activity class needs to present a View with information related to that particular Item and then wait until the user presses the right arrow key to then proceed to the next Item in the Core Data store where the process repeats until all Items have been read. Each time a new Item is read in the loop, the information on the screen should change after the user presses the right arrow each time.
The problem is that I don't know exactly how the best way of going about this should be programatically speaking. I have the code that retrieves the array of Items as an NSFetchRequest:
let moc = (NSApplication.shared.mainWindow?.contentViewController?.representedObject as! NSPersistentDocument).managedObjectContext!
let fetchRequest : NSFetchRequest = Item.fetchRequest()
do {
let items = try moc.fetch(fetchRequest)
print("Found " + String(items.count) + " items to use in the activity.")
for item in items {
print(item.itemType)
// How do I pause this loop for a user keypress after using data from this Item to display?
}
} catch {
print("Error retrieving Items")
}
I can retrieve the keydown event using NSEvent.addLocalMonitorForEvents(matching: .keyDown) and I'm also able to create View Controllers to display the information on a second screen. I just don't know how I should create the 'main loop', so to speak, so that information is displayed and then the app waits until the user presses a key to proceed...
I can share my project code if more information is needed and many thanks to anyone who can enlighten me... :)
You could try using a NSPageController. In your NSPageController you add a ContainerView which will display the ViewControllers that display information for each item. Each ViewController will need a storyboard identifier, e.g. ViewControllerItem1.
Your NSPageController class must conform to the NSPageControllerDelegate protocol and contains an array of ViewControllers to display.
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
arrangedObjects = ["ViewControllerItem1", "ViewControllerItem2", "...","ViewControllerItemN" ]
}
Note about arrangedObjects from the NSPageController documentation: An array containing the objects displayed in the page controller’s view.
Then you implement NSPageControllers viewControllerForIdentifier to return the ViewController that you currently want to display in the ContainerView.
func pageController(_ pageController: NSPageController, viewControllerForIdentifier identifier: String) -> NSViewController {
switch identifier {
case "ViewControllerItem1":
return mainStoryboard().instantiateController(withIdentifier:"ViewControllerItem1") as? ViewControllerItem1
case "...":
default:
}
}
In your action handler for the key down event you implement.
self.navigateForward(sender) or self.navigateBack(sender)
I also implemented this method but I don't remember whether it was required.
func pageControllerDidEndLiveTransition(_ pageController: NSPageController) {
self.completeTransition()
}
I'm looking to move an item in a tableview to a new position. I have models acting as my data source.
var models = [Model]
var dataTableView = UITableView!
A model class looks like this
class Model {
timestamp: Int64
...
}
The models data source is sorted by timestamp, any item inserted should be in it's right position.
When an item's timestamp changes, I want to move it to the corrected sorted position.
What's the best way to do this?
I'm considering just sorting the array to get the item to it's correct position then find it's index and do the following
beginUpdates()
reloadRows(at: [INSERTED_ITEM_INDEX, OLD_INDEX], with: .bottom)
endUpdates()
So that by reloading both index paths, both cells will display correctly.
Another option would be to find which index the item will occupy if inserted into the list (without actually touching the list) and then do the following:
beginUpdates()
moveRow(at: ORIGINAL_INDEX, to: NEW_INDEX)
reloadRows(at: [ORIGINAL_INDEX, NEW_INDEX], with: .bottom)
endUpdates()
Which is the better approach?
None? would be happy to learn new approaches, thanks :)
As we know, in infinite rowModelType, we have to set dataSource for the ag-grid.
const dataSource = {
rowCount: count
getRows: (params: IGetRowsParams) => this.getRows(params, [])
};
this.gridApi.setDatasource(dataSource);
Now, dataSource.getRows method is called whenever there is need to fetch the rows in the grid (due to scrolling) OR the filter is changed.
I need to decide how many ajax calls need to be made depending on this reason. Below code blocks explains this.
private getRows(params: IGetRowsParams, data: any) {
// two ajax calls can be made from here
// 1. getCount
// 2. getData
// if this getRows function is called due to scrolling in the grid,
// I just want to call getData - no need to call getCount as I already know it
// if this is called due to change in filter,
// I need to call getCount as well as the no of rows will be different
// How can I know here due to which above mentioned reasons, getRows is getting called?
}
Is there anyway to know it within getRows function?
I have defined a custom cell with an UISwitch control, is the GetCell method the correct place and correct way to get the values of the cell's control and assign it to a more persistent object than an object from the view? (GetCell method example).
if (indexPath.Section == 0)
{
switch (indexPath.Row)
{
case 0:
TVCellTwoColWBool cell = tableView.DequeueReusableCell(_cIDTwoColWBool) as TVCellTwoColWBool;
if(cell==null)
cell = new TVCellTwoColWBool("Date Filtering", MappedList.DateFilter, _cIDTwoColWBool);
cell.DataView.SWData.ValueChanged += (sender, e) => {MappedList.DateFilter = cell.DataView.SWData.On;};
return cell;
When you create or re-initialize the cell it is a good time to bind the state of any controls in the cell with the actual column/row that you want to attach the behavior to.
Your approach is correct, because it would update the values that you want. But sadly, because you are using ValueChanged as an event, you will be adding a new event handler every time the cell is dequeued.
So you would need to first remove the old event handler, and then add a new event handler. This means that you need to use a helper method for it to allow ValueChanged += FOO and ValueChanged -= FOO
I think this is not GetCell method,this is cellForRowAtIndexPath.
And yes its a correct place to get and paste the values in the cell.