How to force display of Xcode moverow animation? - tableview

I'm trying to reorder a list multiple times and want to see the animation of each individual move, however, the iPhone simulator shows a long delay during the loop, and then it animates the results of all the moves at once:
for _ in 1...1000 {
let source = Int.random(in: 0..<list.count)
let destination = Int.random(in: 0..<list.count)
tableView.moveRow(
at: IndexPath(row: source, section: 0),
to: IndexPath(row: destination, section: 0))
}
I have tried the following commands from Developer Documentation:
tableView.reloadData()
tableView.reloadRows(at:with:)
tableView.reloadSections(at:with:)
tableView.layoutSubviews()
tableView.layoutSublayers(of:)
tableView.layoutIfNeeded()
tableView.beginUpdates()-tableView.endUpdates()
however, they all have the same effect of delaying the animation until the end of the loop.
How can I display the animation of each individual row move within the loop?

Related

Updating rotary knob in real time with SwiftUI

I'm currently trying to convert a slider in to a rotary knob and having a tough time of it all. The knob works in design but i'm struggling to set the correct value within the knob and as a result change the value within the app in real time.
I'm using AVAudio to set up an engine for people to record with that has effects like Reverb and Delay.
The reverb value is set as followed within the Audio Class:
#Published var reverbValue: Float = 0.0
and later on referenced in a function to change it's value
func changeReverbValue() {
setReverb.wetDryMix = reverbValue
}
When I use a regular slider as follows the change works:
Slider(value: $recordingsettings.reverbValue, in: Float(0.0)...recordingsettings.reverbMaxValue, onEditingChanged: { _ in
self.recordingsettings.changeReverbValue()
}).accentColor(Color.white)
As mentioned the knob works fine in its design:
ZStack {
Knobs(color: .orange)
.rotationEffect(
.degrees(max(0, initialCircleState()))
)
.gesture(DragGesture(minimumDistance: 0)
.onEnded({ _ in
startDragValue = -1.0
})
.onChanged { dragValue in
let touchDifferential = touchDifference(dragValue)
setInitialDragVal()
let computedTouch = computeTouch(touchDifferential)
print(computedTouch)
baseValue = getBaseVal(computedTouch)
let normalizeVal = baseValue / touchAmt
value = Float(normalizeVal * rngOffset(range: bounds) + bounds.lowerBound)
print("vaule is: \(value)")
}
)
GrayCircle(bounds: bounds)
OrangeCircle(baseValue: $value, bounds: bounds)
}
.rotationEffect(bounds.lowerBound < 0 ? .degrees(90) : .degrees(107))
I've had some success connecting the knob to the reverb value to the point where the slider also moves when the rotary knob does, however the changeReverbValue function doesn't work.
The success comes from setting the value within the knob view as follows:
#Binding var value: AUValue
And then referencing the knob on the same struct of the main view as the slider:
Knob(value: $recordingsettings.reverbValue, bounds: 0...CGFloat(recordingsettings.reverbMaxValue))
.onTapGesture {
self.recordingsettings.changeReverbValue()
}
The on tap gesture was a way in which I thought it might call the change reverb value function when the knob was turned but to no avail.
The binding value passed in the knob also has other challenges. For some reason when I playback audio without headphones and then turn the knob the audio starts to stutter. This doesn't happen with headphones and I find that pretty weird.
Anyone know how I could reference the reverb value within the rotary knob and have the changeReverbValue function called at the same time?
I just want to replace the slider with something that looks better. Otherwise i'm going to have to leave this for a bit and just implement the sliders instead throughout the app.
If I don't set the value of the knob as #binding in the rotary knob view the track doesn't stutter on playback but then I don't know if it's possible to change the reverb value without a #binding var.
I struggled to parse a precise singular problem statement from the narrative, so this is perhaps just an off-base commentary and not a solution. I walked away thinking your problem is: a custom UI component is "jumpy/stuttery" during interaction and produces similarly punctate effects on app state.
If that's fair, I worked around the same issue in my first SwiftUI app. The cause could be two things:
Not using the right async queue by accident.
Forcing an #State or #Published property to update for all global state changes. That means you are pushing stale state from earlier back into an interaction, possibly with a circular feedback loop.
The solution is pretty simple. Request and consume model updates with both a value and a source tag. Throttle and filter out the self-tag to keep local state responsive to only one just-in-time data stream.
I used that pattern in that first app (free, Inclusivity for Mac) to coordinate an HSV color wheel and color channel custom slider components. The wheel, sliders, and other interactions feed/read a shared Combine pipeline (CurrentValueSubject<SourcedColorVector,Never>.erasedToAny()).
Some sample gestures, which simply punt the gating work to a view model:
The HSVWheel drag-around or click gesture
private func touchUpInWheel() -> ExclusiveGesture<_ChangedGesture<DragGesture>, _EndedGesture<DragGesture>> {
ExclusiveGesture(
DragGesture(minimumDistance: 10, coordinateSpace: .named(wheel))
.onChanged { change in
let adjusted = CGPoint(x: change.translation.width - targetDiameter + change.startLocation.x / 2,
y: change.translation.height - targetDiameter + change.startLocation.y / 2)
vm.setHueSat(drag: adjusted)
},
DragGesture(minimumDistance: 0, coordinateSpace: .named(wheel))
.onEnded { end in
let click = CGPoint(x: end.location.x - vm.radius,
y: end.location.y - vm.radius)
vm.setHueSat(click: click)
}
)
}
A typical slider gesture (this is the vertical value slider)
private func tapAndDrag() -> _EndedGesture<_ChangedGesture<DragGesture>> {
DragGesture(minimumDistance: 0,
coordinateSpace: .named(valuePickerSpace))
.onChanged { value in
let location = value.location.y - .valueSliderGestureOffset
vm.setValueKnobLocation(raw: location)
}
.onEnded { end in
let location = end.location.y - .valueSliderGestureOffset
vm.setValueKnobLocation(raw: location)
}
}

Xcode UITest scrolling to the bottom of an UITableView

I am writing an UI test case, in which I need to perform an action, and then on the current page, scroll the only UITableView to the bottom to check if specific text shows up inside the last cell in the UITableView.
Right now the only way I can think of is to scroll it using app.tables.cells.element(boundBy: 0).swipeUp(), but if there are too many cells, it doesn't scroll all the way to the bottom. And the number of cells in the UITableView is not always the same, I cannot swipe up more than once because there might be only one cell in the table.
One way you could go about this is by getting the last cell from the tableView. Then, run a while loop that scrolls and checks to see if the cell isHittable between each scroll. Once it's determined that isHittable == true, the element can then be asserted against.
https://developer.apple.com/documentation/xctest/xcuielement/1500561-ishittable
It would look something like this (Swift answer):
In your XCTestCase file, write a query to identify the table. Then, a subsequent query to identify the last cell.
let tableView = app.descendants(matching: .table).firstMatch
guard let lastCell = tableView.cells.allElementsBoundByIndex.last else { return }
Use a while loop to determine whether or not the cell isHittable/is on screen. Note: isHittable relies on the cell's userInteractionEnabled property being set to true
//Add in a count, so that the loop can escape if it's scrolled too many times
let MAX_SCROLLS = 10
var count = 0
while lastCell.isHittable == false && count < MAX_SCROLLS {
apps.swipeUp()
count += 1
}
Check the cell's text using the label property, and compare it against the expected text.
//If there is only one label within the cell
let textInLastCell = lastCell.descendants(matching: .staticText).firstMatch
XCTAssertTrue(textInLastCell.label == "Expected Text" && textInLastCell.isHittable)
Blaines answer lead me to dig a little bit more into this topic and I found a different solution that worked for me:
func testTheTest() {
let app = XCUIApplication()
app.launch()
// Opens a menu in my app which contains the table view
app.buttons["openMenu"].tap()
// Get a handle for the tableView
let listpagetableviewTable = app.tables["myTableView"]
// Get a handle for the not yet existing cell by its content text
let cell = listpagetableviewTable.staticTexts["This text is from the cell"]
// Swipe down until it is visible
while !cell.exists {
app.swipeUp()
}
// Interact with it when visible
cell.tap()
}
One thing I had to do for this in order to work is set isAccessibilityElement to true and also assign accessibilityLabel as a String to the table view so it can be queried by it within the test code.
This might not be best practice but for what I could see in my test it works very well. I don't know how it would work when the cell has no text, one might be able to reference the cell(which is not really directly referenced here) by an image view or something else. It's obviously missing the counter from Blaines answer but I left it out for simplicity reasons.

Pause a for Loop for actions - Swift: Sprite Kit

I'm trying to make a deer to jump ramdonly through different defined positions.
func deerJumping () {
// The different locations are arranged ramdonly
var places: [CGFloat] = [1124, 852, 1540, 1908, 628, 1736, 392].shuffled()
//Then the for loop with the actions (jumps) between this positions
for posc in places {
//SKAction for the up and down
//SKAction for the displacement left or right
//Code for the deer to face the direction of the jump
//Then run the action:
deer.run(SKAction.group([jump, move]))
}
}
The problem is that the loop is not waiting for the actions to finish before to go to the next position in the array.
There's this Pause and Resume a for Loop question that I find. It is about to create NSOperation instances in the loop and add them to an NSOperationQueue but sadly I am a beginner and I don't understand the solution so I have no idea how to apply it to my code.
Any help would be much appreciated!!
The reason why nothing is waiting is because you run all of your actions at the same time. You want to create an array of actions and run it in sequence
func deerJumping () {
// The different locations are arranged ramdonly
var places: [CGFloat] = [1124, 852, 1540, 1908, 628, 1736, 392].shuffled()
//Then the for loop with the actions (jumps) between this positions
var actions = [SKAction]()
for posc in places {
//SKAction for the up and down
//SKAction for the displacement left or right
//Code for the deer to face the direction of the jump
//Then run the action:
actions.append(SKAction.sequence([jump, move]))
}
var deerRunning = SKAction.sequence(action)
deer.run(deerRunning)
}
Now I have no idea why you want to pause and resume a for loop, you are going to have to elaborate on that one before I can help you out.

Xcode 7 ui automation - loop through a tableview/collectionview

I am using xCode 7.1. I would like to automate interaction with all cells from a table/collection view. I would expect it to be something like this:
for i in 0..<tableView.cells.count {
let cell = collectionView.cells.elementBoundByIndex(i)
cell.tap()
backBtn.tap()
}
However this snippet only queries current descendants of the table view, so it will loop through the first m (m < n) loaded cells out of total n cells from the data source.
What is the best way to loop through all cells available in data source? Obviously querying for .Cell descendants is not the right approach.
P.S.: I tried to perform swipe on table view after every tap on cell. However it swipes to far away (scrollByOffset is not available). And again, don't know how to extract total number of cells from data source.
Cheers,
Leonid
So problem here is that you cannot call tap() on a cell that is not visible. SoI wrote a extension on XCUIElement - XCUIElement+UITableViewCell
func makeCellVisibleInWindow(window: XCUIElement, inTableView tableView: XCUIElement) {
var windowMaxY: CGFloat = CGRectGetMaxY(window.frame)
while 1 {
if self.frame.origin.y < 0 {
tableView.swipeDown()
}
else {
if self.frame.origin.y > windowMaxY {
tableView.swipeUp()
}
else {
break
}
}
}
}
Now you can use this method to make you cell visible and than tap on it.
var window: XCUIElement = application.windows.elementBoundByIndex(0)
for i in 0..<tableView.cells.count {
let cell = collectionView.cells.elementBoundByIndex(i)
cell.makeCellVisibleInWindow(window, inTableView: tableView)
cell.tap()
backBtn.tap()
}
let cells = XCUIApplication().tables.cells
for cell in cells.allElementsBoundByIndex {
cell.tap()
cell.backButton.tap()
}
I face the same situation however from my trials, you can do tap() on a cell that is not visible.
However it is not reliable and it fails for an obscur reason.
It looks to me that this is because in some situation the next cell I wanted to scroll to while parsing my table was not loaded.
So here is the trick I used:
before parsing my tables I first tap in the last cell, in my case I type an editable UITextField as all other tap will cause triggering a segue.
This first tap() cause the scroll to the last cell and so the loads of data.
then I check my cells contents
let cells = app.tables.cells
/*
this is a trick,
enter in editing for last cell of the table view so that all the cells are loaded once
avoid the next trick to fail sometime because it can't find a textField
*/
app.tables.children(matching: .cell).element(boundBy: cells.count - 1).children(matching: .textField).element(boundBy: 0).tap()
app.typeText("\r") // exit editing
for cellIdx in 0..<cells.count {
/*
this is a trick
cell may be partially or not visible, so data not loaded in table view.
Taping in it is will make it visible and so do load the data (as well as doing a scroll to the cell)
Here taping in the editable text (the name) as taping elsewhere will cause a segue to the detail view
this is why we just tap return to canel name edidting
*/
app.tables.children(matching: .cell).element(boundBy: cellIdx).children(matching: .textField).element(boundBy: 0).tap()
app.typeText("\r")
// doing my checks
}
At least so far it worked for me, not sure this is 100% working, for instance on very long list.

UI Testing in Xcode 7: Selecting a row number in a UIPickerView / PickerWheel

Is there a way in Xcode 7 UI Testing to select the 3rd row in a UIPickerView?
I have tried various things like this to identify the rows in each picker but all the requests below return 0 found:
XCUIApplication().pickers.element.cells.count
XCUIApplication().pickers.element.staticTexts.count
Any ideas?
Updated: I am aware of adjustToPickerWheelValue method where you can select a particular value you already know, but I am trying to select the 3rd value (index = 4) when I don't know the values that exist in the picker.
You can use -adjustToPickerWheelValue: to select items on a UIPickerView.
When there is one UIPickerView on the screen you can select the element directly, like so.
let app = XCUIApplication()
app.launch()
app.pickerWheels.element.adjustToPickerWheelValue("Books")
If the picker has multiple wheels you will need to first select the wheel via it's accessibility identifier, then adjust it.
app.pickerWheels["Feet"].adjustToPickerWheelValue("5")
Here's a GitHub repo with a working example. And some more information in a blog post I wrote.
Workaround:
let pickerWheel = XCUIApplication().pickers.pickerWheels.element
let appWindowHeight = XCUIApplication().windows.elementBoundByIndex(0).frame.height
let pickerWheelCellHeight = GFloat(44)
func advancePickerWheelOneValue() -> Self {
let topScrollPoint = Double(pickerWheel.frame.midY/appWindowHeight)
let bottomScrollPoint = Double((pickerWheel.frame.midY + pickerWheelCellHeight)/appWindowHeight)
let topScreenPoint = XCUIApplication().windows.elementBoundByIndex(0).coordinateWithNormalizedOffset(CGVector(dx: 0.5, dy: topScrollPoint))
let bottomScreenPoint = XCUIApplication().windows.elementBoundByIndex(0).coordinateWithNormalizedOffset(CGVector(dx: 0.5, dy: bottomScrollPoint))
bottomScreenPoint.pressForDuration(0, thenDragToCoordinate: topScreenPoint)
return self
}
You may need to adjust the dx value depending on where the picker is.
.coordinateWithNormalizedOffset(CGVector(dx: 0.5,
But 0.5 works if the picker occupies the width of the screen.
With this method you an move the picker on value at a time. Call it multiple times and you get to the value you want.
Its not ideal but it can help if you want to check certain indexes. Hopefully you would have to use it on a picker with hundreds of values ;)
Example with Date picker (UIDatePicker)
XCUIApplication().datePickers.pickerWheels.elementBoundByIndex(0).adjustToPickerWheelValue("March")
XCUIApplication().datePickers.pickerWheels.elementBoundByIndex(1).adjustToPickerWheelValue("24")
XCUIApplication().datePickers.pickerWheels.elementBoundByIndex(2).adjustToPickerWheelValue("2000")
And here is example with Picker view (UIPickerView)
XCUIApplication().pickerWheels.element.adjustToPickerWheelValue("Male")

Resources