Call a method in scrollViewDidScroll with low frequency - swift

I have a method that manage constraints, while some conditions are met. That method is placed inside scrollViewDidScroll methood:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
partiallyHideOverallViewOnScrolling(scrollView)
scrollToNewRowsIfNeeded()
}
Recently I discover freezes and weird behaviour while scrolling table, is there are possibility to call method inside scrollViewDidScroll no more then, for example, every 0.1 second?
It appears that method called 111 times on single drag down, when there is not much items in a tableView, that's quite a lot

even though Dogan Altinbas's comment suggesting to use scrollViewWillBeginDragging and scrollViewWillEndDragging sounds like the right approach to me I'd like to answer your question.
Just a quick example I created in a playground:
import UIKit
var date = Date()
while (true) {
if Date().timeIntervalSince(date) > 1 {
print("foo")
date = Date()
}
}
You'll see it'll only print once per second. I just remember the last time I fired the event I wanted to fire and go from there.
Cheers,
Dominic

Related

SwiftUI XCTest - How to increase velocity of scrolling?

I wrote a method that allows me to scroll list of elements on the screen for UI testing:
func scrollToElement(element: XCUIElement, maxScrolls: Int = 10) {
var numberOfScrolls = 0
while !element.isHittable, numberOfScrolls < maxScrolls {
app.swipeUp()
numberOfScrolls += 1
}
}
Everything works fine but it takes too much time for the UI test to verify ie. wait until every scrolled view will be checked whether the element is available.
Is there any possible method or way to increase the velocity of scrolling?
Thanks in advance!

Using Swift, how do I animate the .setPosition() method of an NSSplitView without visually stretching its contents?

I would like to animate the appearance of a NSSplitViewItem using .setPosition() using Swift, Cocoa and storyboards. My app allows a student to enter a natural deduction proof. When it is not correct, an 'advice view' appears on the right. When it is correct, this advice view will disappear.
The code I'm using is the below, where the first function makes the 'advice' appear, and the second makes it disappear:
func showAdviceView() {
// Our window
let windowSize = view.window?.frame.size.width
// A CGFloat proportion currently held as a constant
let adviceViewProportion = BKPrefConstants.adviceWindowSize
// Position is window size minus the proportion, since
// origin is top left
let newPosition = windowSize! - (windowSize! * adviceViewProportion)
NSAnimationContext.runAnimationGroup { context in
context.allowsImplicitAnimation = true
context.duration = 0.75
splitView.animator().setPosition(newPosition, ofDividerAt: 1)
}
}
func hideAdviceView() {
let windowSize = view.window?.frame.size.width
let newPosition = windowSize!
NSAnimationContext.runAnimationGroup{ context in
context.allowsImplicitAnimation = true
context.duration = 0.75
splitView.animator().setPosition(newPosition, ofDividerAt: 1)
}
}
My problem is that the animation action itself is causing the text in the views to stretch, as you can see in this example: Current behaviour
What I really want is the text itself to maintain all proportions and slide gracefully in the same manner that we see when the user themselves moves the separator: Ideal behaviour (but to be achieved programmatically, not manually)
Thus far in my troubleshooting process, I've tried to animate this outside of NSAnimationContext; played with concurrent drawing and autoresizing of subviews in XCode; and looked generally into Cocoa's animation system (though much of what I've read doesn't seem to have direct application here, but I might well be misunderstanding it). I suspect what's going on is that the .animator() proxy object allows only alpha changes and stretches---redrawing so that text alignment is honoured during the animation might be too non-standard. My feeling is that I need to 'trick' the app into treating the animation as though it's being performed by the user, but I'm not sure how to go about that.
Any tips greatly appreciated...
Cheers

How to suspend a work item on the main queue

I want to know if it is possible to suspend and then resume a work item on the main queue whilst maintaining the '.asyncAfter' time. If not, is there a workaround to achieve this?
At a certain point, I queue up the following DispatchWorkItem:
dispatchWorkItem = DispatchWorkItem(qos: .userInteractive, block: {
self.view.backgroundColor = UIColor.workoutBackgroundColor
self.runTimer()
self.timerButton.animateableTrackLayer.removeAnimation(forKey: "strokeEndAnimation")
self.isRestState = false
})
I queue this up using:
DispatchQueue.main.asyncAfter(deadline: delayTime, execute: self.dispatchWorkItem))
(delayTime being a parameter to the function)
Now, the problem I am running into is how can I suspend this work item if the user performs a 'pause' action in my app.
I have tried using the DispatchQueue.main.suspend() method but the work item continues to execute after the specified delay time. From what I have read, this method should suspend the queue and this queued work item since it is not being executed. (Please correct me if I am wrong there!)
What I need to achieve is the work item is 'paused' until the user performs the 'resume' action in the app which will resume the work item from where the delay time left off.
This works on background queues that I have created when I do not need to make UI updates; however, on the main queue is appears to falter.
One workaround I have considered is when the user performs the pause action, storing the time left until the work item was going to be executed and re-adding the work item to the queue with that time on the resume action. This seems like a poor quality approach and I feel there is a more appropriate method to this.
On that, is it possible to create a background queue that on execution, executes a work item on the main queue?
Thanks in advance!
On that, is it possible to create a background queue that on execution, executes a work item on the main queue?
You are suggesting something like this:
var q = DispatchQueue(label: "myqueue")
func configAndStart(seconds:TimeInterval, handler:#escaping ()->Void) {
self.q.asyncAfter(deadline: .now() + seconds, execute: {
DispatchQueue.main.async(execute: handler())
})
}
func pause() {
self.q.suspend()
}
func resume() {
self.q.resume()
}
But my actual tests seem to show that that won't work as you desire; the countdown doesn't resume from where it was suspended.
One workaround I have considered is when the user performs the pause action, storing the time left until the work item was going to be executed and re-adding the work item to the queue with that time on the resume action. This seems like a poor quality approach and I feel there is a more appropriate method to this.
It isn't poor quality. There is no built-in mechanism for pausing a dispatch timer countdown, or for introspecting the timer, so if you want to do the whole thing on the main queue your only recourse is just what you said: maintain your own timer and the necessary state variables. Here is a rather silly mockup I hobbled together:
class PausableTimer {
var t : DispatchSourceTimer!
var d : Date!
var orig : TimeInterval = 0
var diff : TimeInterval = 0
var f : (()->Void)!
func configAndStart(seconds:TimeInterval, handler:#escaping ()->Void) {
orig = seconds
f = handler
t = DispatchSource.makeTimerSource()
t.schedule(deadline: DispatchTime.now()+orig, repeating: .never)
t.setEventHandler(handler: f)
d = Date()
t.resume()
}
func pause() {
t.cancel()
diff = Date().timeIntervalSince(d)
}
func resume() {
orig = orig-diff
t = DispatchSource.makeTimerSource()
t.schedule(deadline: DispatchTime.now()+orig, repeating: .never)
t.setEventHandler(handler: f)
t.resume()
}
}
That worked in my crude testing, and seems to be interruptible (pausable) as desired, but don't quote me; I didn't spend much time on it. The details are left as an exercise for the reader!

Increase Node Speed with SKPhysicsBody linearDamping Swift 4

I have the below class being called from my GameScene
func update(_ currentTime: TimeInterval)
Since it's being called there, my class is being called every second which leads me to this. I currently have my self.physicsBody?.linearDamping = 0.6 but how would I increase that number so I can also increase the speed? I was going to use another Timer till I realized my SKSpriteNode class is being called every second. Not too sure how to go about this, any ideas? I basically want to decrease that number every 2.0 seconds without letting the update function get in the way.
Any time you want to do something at regular time intervals in a sprite-Kit game, you can implement this as follows:
First declare 2 properties (in your class but outside all the function definitions)
var timeOfLastThing: CFTimeInterval = 0.0
var timePerThing: CFTimeInterval = 2.0 // How often to do the thing
( if the thing you want to do every 2 seconds is spawn a monster, then you might call these timeOfLastMonsterSpawn and timePerMonsterSpawn, but it's up to you)
Then, in Update, check to see if the timePerThing has been exceeded. If so, call your doThing function which does what you need it to and then reset the time since the last call:
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
if (currentTime - timeOfLastThing > timePerThing) {
doThing()
self.timeOfLastThing = currentTime
}
}
func doThing() {
// Your spawn code here
}
The advantage of making the doThing a separate function (as opposed to it being in line in update())is that you can call it from didMoveToView or any other place to spawn objects outside of the normal time-controlled cycle.
You can change the value of timePerThing as necessary to control the rate at which things happen.
You could also look into creating an SKAction that runs doThing at specified time intervals, but I think to change the rate at which objects are spawned, you'll have to delete and re-create the SKAction, which you could do this in a setter for timePerThing.
You shouldn't really use NSTimer in SpriteKit, as the SpriteKit engine will be unaware of what the timer is doing and can't control it (one example is that the timer keeps running and doing stuff even if you set the scene to paused).

sceneDidLoad Running Twice

When I run my program. The code I put into "override func sceneDidLoad()" runs two times.
E.g.
Note: I have no idea why this picture is not uploading, but it shows "spawn" happening twice.
This code should only run once when "sceneDidLoad()" is called.
Here is the code for the "sceneDidLoad" function, and for the "testSpawn()" function (which is the specific one that gave the duplicated printout).
class GameScene: SKScene {
var mapTerrain: SKTileMapNode!
override func sceneDidLoad() {
cam = SKCameraNode()
cam.xScale = 1
cam.yScale = 1
//do zoom by change in scale in pinch. (E.g. if they start out 5 units apart and end up 15 units apart, zoom by a factor of 3
self.camera = cam
self.addChild(cam)
cam.position = CGPoint(x: 100, y: 100)
setupLayers()
loadSceneNodes()
setUpUI()
testSpawn()
//print("\(self.frame.width), \(self.frame.height)")
}
func testSpawn(){
let RedLegion = legion(texture: textureRedLegion, moveTo: nil, tag: 1, health: 2)
RedLegion.position = mapTerrain.centerOfTile(atColumn: 0, row: 0)
RedLegion.team = "Red"
unitsLayer.addChild(RedLegion)
legionList.append(RedLegion)
print("spawn")
}
}
Note: Not all of the code is here (like "setUpLayers()"), if needed I can supply it, I just do not think it is neccessary.
Search your whole document for "print("spawn")" just to make sure that is the only time you call the function. Also check for "testSpawn()" to make sure it is only called once. Additionally, instead of relying on this print to count how many times the sceneDidLoad runs, place a print directly within your sceneDidLoad. Finally, check to make sure you are not creating the scene twice.
I've also seen this and submitted a bug report but apple responded saying that it is intended behavior. Apple said that it creates a dummy scene and then creates the actual scene. Before it runs the second time it gets rid of anything done the first time so you shouldn't get any errors from it. The bug is really hard to reproduce, one of my friends working off the same repository that I was but did not experience the bug.
I changed sceneDidLoad to didMoveToView:(SKView *)view if you are looking for a solution to this. Make sure you xcode is up to date.