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 :)
Related
I created a View that displays a line graph based on an array of Doubles. The graph struct then divides the view horizontally in order for each of the points to have the same amount of separation. I am using GeometryReader and Path to make the graph. When I load the View with a given array of Doubles it displays correctly.
However, when the data changes, such as adding or removing data, the graph is not updated properly.
As an example using text, this is what the graph looks like normally (Assume it is a line graph): |-_-_-_-_|
When I add a value to the array of Doubles, the graph does appear to show that a new value was added, but the value is not recognized or read. This is what it looks like when a value is added (Again, assume it is a line graph): |-_-_-_-_ |. As you can see, it recognized there is a new value because it allocated space for the new value, but no line was shown, as it most likely does not recognize the added value. When I reload the view, such as by going back and into the view again, the graph is displayed correctly.
The big problem comes when a value is deleted. When a value is deleted, the app crashes with the error Fatal error: Index out of range. When I load the app again the graph is fixed.
I thought there might be something wrong with my deleting, so I created a test file in which I referenced an array of Doubles and buttons to add and remove values from the array, and the same behavior happened. This showed that the values put into the View were not updated properly.
This is the file I created to test the behavior of my graph:
struct GraphTest: View {
/*Data*/
#State var data: [Double] = [180, 242, 164, 202, 176, 100]
var body: some View {
VStack {
/*Struct that shows the line graph*/
LineGraph(data: data)
.frame(height: 200)
/*Button to remove the last item of array*/
Button(action: {
data.removeLast()
}) {
Text("Delete")
}
/*Button to add a value to the end of the array*/
Button(action: {
data.append(180)
}) {
Text("Add")
}
}
}
}
And this is how I'm setting up my LineGraph struct:
struct LineGraph: View {
var data: [Double]
var body: some View {
GeometryReader { geometry in
...
I tried doing data #Bindable in LineGraph, but I could not find a way to do so, as I'm getting the data from a Core Data FetchRequest, but I really do not know what to do from now to fix the issue, as I am relatively new in SwiftUI, and could not find anything similar on the internet.
Any help is appreciated, thank you :)
There is not enough code provided in LineGraph, but I assume you have inside something like
ForEach(0..<data.count) { i in // iterating this way result in crash !!
Text("\(self.data[i])")
}
instead you have to iterate at least like
ForEach(data.indices, id: \.self) { i in // no crash !!
Text("\(self.data[i])")
}
I have a collection view which is loaded based on the API(Get method).The number of entries increases when new entry is added and it is reloaded with new entry at the end.
How to reload the collection view with last showing first, second last as second and so on.
If you want to display array in reverse then you can do it like this:-
array.reversed()
OR
if you want to display last added item on top, then insert item at first index:-
yourArray.append(item)
let indexPath = IndexPath(row: 0, section: 0)
collectionView.insertItems(at: [indexPath])
I'm testing a tableview the cell content in XCUItest. In my case, I don't know the order of the cell text, nor am I allowed to set an accessibility id for the text. How can I get the index of a cell given the text inside?
For instance, if I wanted to get the index of the cell containing text "Cell 2 Text" I would try something like this:
func testSample() {
let app = XCUIApplication()
let table = app.tables
let cells = table.cells
let indexOfCell2Text = cells.containing(.staticText, identifier: "Cell 2 Text").element.index(ofAccessibilityElement: "I dunno")
print(indexOfCell2Text)
}
I feel like I'm close, but I'm unsure. Can anyone suggest a solution?
I apologize if this question has been asked before. I wasn't able to find anything specific about this.
References I visited beforehand:
https://developer.apple.com/documentation/xctest/xcuielementquery/1500842-element
How can I verify existence of text inside a table view row given its index in an XCTest UI Test?
iOS UI Testing tap on first index of the table
The most reliable way really is to add the index into the accessibility identifier. But, you can't. Can you change the accessibility identifier of the cell instead of the text ?
Anyway, if you don't scroll your table view, you can handle it like that :
let idx = 0
for cell in table.cells.allElementsBoundByIndex {
if cell.staticTexts["Text you are looking for"].exists {
return idx
}
idx = idx + 1
}
Otherwise, the index you will use is related to cells which are displayed on the screen. So, after scrolling, the new first visible cell would become the cell at index 0 and would screw up your search.
for index in 0..<table.cells.count {
if table.cells.element(boundBy: index).staticTexts["Your Text"].exists {
return index
}
}
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()
}
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.