Loop forth and back through image array _ SwiftUI - image

I'm trying to make an animation from 5 pictures in my App. I want to click on a displayed Image and then start the animation that goes forth and back through array and ends/stops again on the first picture.
So far I've got this - I loop from 0 to 4 and then it just keep looping between 4 & 3.
Do you have any ideas how could I fix it?
Thanks!
struct ContentView: View {
#State private var activeImageIndex = 0
#State private var startTimer = false
let timer = Timer.publish(every: 0.15, on: .main, in: .common).autoconnect()
let myShots = ["MuscleUp1", "MuscleUp2", "MuscleUp3", "MuscleUp4", "MuscleUp5"]
var body: some View {
VStack {
Image(self.myShots[self.activeImageIndex])
.resizable()
.aspectRatio(contentMode: .fit)
.onReceive(self.timer) { time in
if self.startTimer {
if self.activeImageIndex != 4 {
self.activeImageIndex += 1
} else {
self.activeImageIndex -= 1
}
}
}
.onTapGesture {
self.startTimer.toggle()
}
}
.background(Color.blue)
}
}

You seem to have a problem in the if statement used to increment activeImageIndex:
if self.activeImageIndex != 4 {
self.activeImageIndex += 1
} else {
self.activeImageIndex -= 1
}
When activeImageIndex starts and successfully goes to 4, it will then run like this:
if self.activeImageIndex != 4 4 == 4, therefore enters the else case self.activeImageIndex -= 1, activeImageIndex is now 3.
if self.activeImageIndex != 4 3 != 4, therefore enters the if case self.activeImageIndex += 1, activeImageIndex is now 4 again.
Then this repeats forever.

You can add private var eg. _up
..
#State private var _up = true
......
and modify .onReceive
.onReceive(self.timer) { time in
if self.startTimer {
if self.activeImageIndex != 4 && self._up {
self.activeImageIndex += 1
if self.activeImageIndex == 4 {
self._up = false
}
} else {
self.activeImageIndex -= 1
if self.activeImageIndex == 0 {
self._up = true
}
}
}

Related

FlatMapLatest with Replay

I have a situation where I have three observables that map to a result stream.
The first observable tells me which of two other observables I should take values from. On every emission of the first observable, the result stream switches to emitting the flatmap of the appropriate observable.
This is a situation that normally can be accomplished via flatMapLatest.
However, on every switch of the flatMap, I also happen to want to replay the latest stale value. This is an issue, because flatMapLatest does not provide for an ability for a replay on old values of existing observables.
See below for an RxMarbles of what I want to accomplish:
I tried using shareReplay(N), as in the code below, but that does not seem to solve the issue.
let Observable1Replay = Observable1.share(replay: 1, scope: .forever)
let Observable2Replay = Observable2.share(replay: 1, scope: .forever)
let resultObservable = Observable3
.flatMapLatest { boolValue in
if boolValue == true {
return Observable1Replay
} else {
return Observable2Replay
}
}
When you are stuck, you can always roll your own and come back to it later:
func example(source: Observable<String>, obs1: Observable<String>, obs2: Observable<String>) -> Observable<String> {
return Observable.create { observer in
let lock = NSRecursiveLock()
var last1 = ""
var last2 = ""
var isFirst = false
var completeCount = 3
let srcSub = source.subscribe { event in
lock.lock(); defer { lock.unlock() }
switch event {
case .next:
isFirst = !isFirst
if isFirst && !last1.isEmpty {
observer.onNext(last1)
}
else if !isFirst && !last2.isEmpty {
observer.onNext(last2)
}
case .error(let error):
observer.onError(error)
case .completed:
completeCount -= 1
if completeCount == 0 {
observer.onCompleted()
}
}
}
let obs1Sub = obs1.subscribe { event in
lock.lock(); defer { lock.unlock() }
switch event {
case .next(let element):
if isFirst {
observer.onNext(element)
}
last1 = element
case .error(let error):
observer.onError(error)
case .completed:
completeCount -= 1
if completeCount == 0 {
observer.onCompleted()
}
}
}
let obs2Sub = obs2.subscribe { event in
lock.lock(); defer { lock.unlock() }
switch event {
case .next(let element):
if !isFirst {
observer.onNext(element)
}
last2 = element
case .error(let error):
observer.onError(error)
case .completed:
completeCount -= 1
if completeCount == 0 {
observer.onCompleted()
}
}
}
return CompositeDisposable(srcSub, obs1Sub, obs2Sub)
}
}

propagate MultiPointTouchArea event

It seems like there is no easy way to do this
For example :
Window {
id: window
width: 480
height: 640
MouseArea {
anchors.fill: parent
onClicked: console.debug("MouseArea Clicked !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
preventStealing: true
}
TwoFingerArea {
anchors.fill: parent
enabled: true
dragEnabled: true
onZoomIn: console.debug("ZoomIn")
onZoomOut: console.debug("ZoomOut")
onDragLeft: console.debug("DragLeft")
onDragRight: console.debug("DragRight")
onDragUp: console.debug("DragUp")
onDragDown: console.debug("DragDown")
}
}
TwoFingerArea :
MultiPointTouchArea {
id: touch
minimumTouchPoints: 2
maximumTouchPoints: 2
property var startPoint
property var startScale
property bool gestureDone: false
property bool dragEnabled: true
property int threshold: width / 12
function dist(point1, point2) { return Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)) }
function centerOf(point1, point2) { return Qt.point((point1.x + point2.x) / 2, (point1.y + point2.y) / 2) }
signal zoomIn
signal zoomOut
signal dragLeft
signal dragRight
signal dragUp
signal dragDown
onPressed: {
if (touchPoints.length === 2) {
startPoint = centerOf(touchPoints[0], touchPoints[1])
startScale = dist(touchPoints[0], touchPoints[1])
gestureDone = false
}
}
onTouchUpdated: {
console.debug(touchPoints.length)
if (touchPoints.length === 2 && !gestureDone) {
var center = centerOf(touchPoints[0], touchPoints[1])
var distance = dist(center, startPoint)
var scale = dist(touchPoints[0], touchPoints[1])
if (scale - startScale > threshold) { zoomIn(); gestureDone = true }
else if (scale - startScale < -threshold) { zoomOut(); gestureDone = true }
else if (dragEnabled && distance > threshold) {
var distX = startPoint.x - center.x
var distY = startPoint.y - center.y
var xBigger = Math.abs(distX) >= Math.abs(distY)
if (xBigger && distX >= 0) { dragLeft(); gestureDone = true }
else if (xBigger && distX < 0) { dragRight(); gestureDone = true }
else if (distY >= 0) { dragUp(); gestureDone = true }
else { dragDown(); gestureDone = true }
}
}
}
}
Even though I told MultiPointTouchArea to only care about 2 points events, it will still capture 1 point event, and prevent the mouseArea from receiving the signal.
In the end, you can't have a touch gesture on top of a MouseArea of a Flickable or any mouse event item because they'll never receive any signal
Do you have any idea how to get around the problem ?
This is apparently a bug, I reported it there :
https://bugreports.qt.io/browse/QTBUG-49992

Simulating human mouse movement

Hi I'm currently trying to create a program that moves the cursor from a given point to another in one smooth randomised motion. I currently have created the following using CoreGraphics, which works but the mouse movement gets very choppy. Any ideas on how to fix this? Much appreciated. I call the following at the start of my Mac OS X Application inside applicationDidFinishLaunching:
var pos = NSEvent.mouseLocation()
pos.y = NSScreen.mainScreen()!.frame.height - pos.y
moveMouse(CGPoint(x:200,y:200), from: pos)
And these are the functions I've created:
func transMouse(point:CGPoint) {
let move = CGEventCreateMouseEvent(nil, CGEventType.MouseMoved, point, CGMouseButton.Left)
CGEventPost(CGEventTapLocation.CGHIDEventTap, move)
}
func moveMouseOne(direction:Character, _ currentPos:CGPoint) -> CGPoint {
var newPos = currentPos
if direction == "r" {
newPos.x = currentPos.x + 1
} else if direction == "l" {
newPos.x = currentPos.x - 1
} else if direction == "u" {
newPos.y = currentPos.y - 1
} else if direction == "d" {
newPos.y = currentPos.y + 1
}
transMouse(newPos)
return newPos
}
func moveMouse(to:CGPoint, from:CGPoint) -> CGPoint {
let dx:Int = Int(to.x - from.x)
let dy:Int = Int(to.y - from.y)
var moves = Array<Character>()
if dx > 0 {
for _ in 0..<dx {
moves.append("r")
}
} else {
for _ in 0..<(-dx) {
moves.append("l")
}
}
if dy > 0 {
for _ in 0..<dy {
moves.append("d")
}
} else {
for _ in 0..<(-dy) {
moves.append("u")
}
}
var pos = from
let delay:Double = 0.0008
let startTime = DISPATCH_TIME_NOW
for var i = 0; i < moves.count; ++i {
let time = dispatch_time(startTime, Int64(delay * Double(i) * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue()) {
let count = moves.count
let random = Int(arc4random_uniform(UInt32(count)))
pos = self.moveMouseOne(moves[random], pos)
if random == count - 1 {
moves.popLast()
} else {
moves[random] = moves.popLast()!
}
}
}
return to
}
I really recommend using Core Animation for something like this, will save you a lot of time and effort.
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html

Drawing performance with multiple UIBezierPaths

I have a drawing app which is currently made up of a main View Controller which holds 4 separate UIViews which simultaneously replicate the line drawn on the touched quadrant across the other 3 with some axis reversed to make the drawing symmetrical.
When using this method the drawing is smooth and you can see that there are lots of points being collected when the user moves their finger as the line follows their movements quite well.
The code at a high level looks like this:
MainViewController.swift
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
var touch: UITouch = touches.anyObject() as UITouch
var p = CGPoint()
if touch.view == quadrant1 {
p = touch.locationInView(quadrant1)
quadrant1.began(p)
var p2 = CGPointMake(quadrant2.bounds.width - p.x, p.y)
quadrant2.began(p2)
var p3 = CGPointMake(p.x,quadrant3.bounds.height - p.y)
quadrant3.began(p3)
var p4 = CGPointMake(quadrant4.bounds.width - p.x, quadrant4.bounds.height - p.y)
quadrant4.began(p4)
} else if touch.view == quadrant2 {
...
Touches 'moved' and 'ended' call similar methods in each of the quadrants by doing the same calculations. The Quadrant files look like this:
Quadrant1,2,3,4.swift
// A counter to determine if there are enough points to make a quadcurve
var ctr = 0
// The path to stroke
var path = UIBezierPath()
// After the user lifts their finger and the line has been finished the same line is rendered to an image and the UIBezierPath is cleared to prevent performance degradation when lots of lines are on screen
var incrementalImage = UIImage()
// This array stores the points that make each line
var pts: [CGPoint] = []
override func drawRect(rect: CGRect) {
incrementalImage.drawInRect(rect)
path.stroke()
}
func began (beganPoint: CGPoint) {
ctr = 0
var p = beganPoint
pts.insert(beganPoint, atIndex: 0)
}
func moved(movedPoints: CGPoint) {
var p = movedPoints
ctr++
pts.insert(movedPoints, atIndex: ctr)
// This IF statement handles the quadcurve calculations
if ctr == 3 {
pts[2] = CGPointMake((pts[1].x + pts[3].x)/2.0, (pts[1].y + pts[3].y)/2.0);
path.moveToPoint(pts[0])
path.addQuadCurveToPoint(pts[2], controlPoint: pts[1])
self.setNeedsDisplay()
pts[0] = pts[2]
pts[1] = pts[3]
ctr = 1
}
}
func ended (endPoint: CGPoint) {
if ctr == 2 {
path.moveToPoint(pts[0])
path.addQuadCurveToPoint(pts[2], controlPoint: pts[1])
}
self.drawBitmap()
self.setNeedsDisplay()
path.removeAllPoints()
}
func drawBitmap() {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
var rectPath = UIBezierPath(rect: self.bounds)
UIColor.clearColor().setFill()
rectPath.fill()
incrementalImage.drawAtPoint(CGPointZero)
color.setStroke()
path.stroke()
incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
So the above approach actually worked very well and produce fairly smooth lines like so but the user was always locked into using 4 quadrants because they were separate UIView's:
After some thinking we decided to scrap the 4 separate UIView's and use a single view to handle the drawing which would allow an arbitrary number of lines to be drawn at a time giving the user more options (8 lines for example), and this is where things got tricky.
The MainViewController no longer handles the touches interaction methods, the new 'DrawingView' captures the gestures itself with a UILongPressGestureRecogniser.
func handleLongPressDrawing(sender: UILongPressGestureRecognizer) {
var p = sender.locationInView(self)
switch sender.state {
case UIGestureRecognizerState.Began:
self.began(p)
break;
case UIGestureRecognizerState.Changed:
self.moved(p)
break;
case UIGestureRecognizerState.Ended:
self.ended(p)
default:
break;
}
}
The methods now reference a new DrawingElement class to perform the symmetry calculations:
enum GridType {
case ONE, TWO_1, TWO_2, TWO_3, TWO_4, THREE, FOUR_1, FOUR_2, FIVE, SIX_1, SIX_2, SEVEN, EIGHT_1, SIXTEEN
}
enum DrawingElementType {
case PATH, POINT, CIRCLE
}
class DrawingElement: NSObject {
var points : [CGPoint] = []
private var drawingWidth : CGFloat!
private var drawingHeight : CGFloat!
private var gridType : GridType!
private var drawingElementType : DrawingElementType!
init(gridType : GridType, drawingWidth : CGFloat, drawingHeight : CGFloat) {
self.gridType = gridType
self.drawingWidth = drawingWidth
self.drawingHeight = drawingHeight
super.init()
}
func getPoints() -> [CGPoint] {
return points
}
func addPoint(pointCG: CGPoint) {
points.append(pointCG)
}
func getPoint(pos : Int) -> CGPoint {
return points[pos]
}
func getDrawingWidth() -> CGFloat {
return drawingWidth
}
func setDrawingWidth(w : CGFloat) {
drawingWidth = w
}
func getDrawingWidthCG() -> CGFloat {
return CGFloat(drawingWidth)
}
func getDrawingHeight() -> CGFloat {
return drawingHeight
}
func setDrawingHeight(h : CGFloat) {
drawingHeight = h
}
func getDrawingHeightCG() -> CGFloat {
return CGFloat(drawingHeight)
}
func getPointCount() -> Int {
return points.count
}
func getDrawingElementType() -> DrawingElementType {
return drawingElementType
}
func setDrawingElementType(det : DrawingElementType) {
drawingElementType = det
}
func getGridType() -> GridType {
return gridType
}
func setGridType(gt : GridType) {
gridType = gt
}
func smoothLinesPart1() {
points[2] = CGPointMake((points[1].x + points[3].x)/2.0, (points[1].y + points[3].y)/2.0)
}
func smoothLinesMoveTo() -> CGPoint {
return points[0]
}
func smoothLinesQuadCurve() -> (CGPoint, CGPoint) {
return (points[2], points[1])
}
func smoothLinesReorderArray() {
points[0] = points[2]
points[1] = points[3]
}
func getCalculatedPoints(allPoints : [CGPoint]) -> [Int : [CGPoint]] {
var newPoints = [CGPoint]()
var numberOfPoints : Int!
var temp : CGFloat!
var x : CGFloat!
var y : CGFloat!
//println("Before Path points: \(allPoints)")
var pathPoints = [Int() : [CGPoint]()]
if(gridType == GridType.EIGHT_1) {
numberOfPoints = 8
} else if(gridType == GridType.ONE) {
numberOfPoints = 1
} else if(gridType == GridType.TWO_1) {
numberOfPoints = 2
} else if(gridType == GridType.FOUR_1) {
numberOfPoints = 4
}
var firstTime = true
for point in allPoints {
x = point.x
y = point.y
if(gridType == GridType.EIGHT_1 || gridType == GridType.ONE || gridType == GridType.TWO_1 || gridType == GridType.FOUR_1) {
if(firstTime) {
for i in 1...numberOfPoints {
switch (i) {
case 5:
temp = y;
y = x;
x = temp;
pathPoints[4] = [CGPoint(x: x, y: y)]
case 1:
pathPoints[0] = [CGPoint(x: x, y: y)]
//println(" first point\(pathPoints[0])")
break;
case 2:
pathPoints[1] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y)]
break;
case 6:
pathPoints[5] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y)]
break;
case 3:
pathPoints[2] = [CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1)]
break;
case 7:
pathPoints[6] = [CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1)]
break;
case 4:
pathPoints[3] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1)]
break;
case 8:
pathPoints[7] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1)]
break;
default:
break
//newPoints.append(CGPoint(x: x, y: y))
}
}
firstTime = false
} else {
for i in 1...numberOfPoints {
switch (i) {
case 5:
temp = y;
y = x;
x = temp;
pathPoints[4]?.append(CGPoint(x: x, y: y))
case 1:
pathPoints[0]?.append(CGPoint(x: x, y: y))
//println(" first point\(pathPoints[0])")
break;
case 2:
pathPoints[1]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y))
break;
case 6:
pathPoints[5]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y))
break;
case 3:
pathPoints[2]?.append(CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1))
break;
case 7:
pathPoints[6]?.append(CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1))
break;
case 4:
pathPoints[3]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1))
break;
case 8:
pathPoints[7]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1))
break;
default:
break
//newPoints.append(CGPoint(x: x, y: y))
}
}
}
}
}
}
And this is called at various parts of the DrawingViews interaction handlers:
var paths = [Int() : UIBezierPath()]
func began (beganPoint: CGPoint) {
strokes = 0
var p = beganPoint
ctr = 0
//pts.insert(beganPoint, atIndex: 0)
drawingElement?.addPoint(beganPoint)
}
func moved(movedPoints: CGPoint) {
strokes++
var p = movedPoints
ctr++
drawingElement?.addPoint(movedPoints)
if ctr == 3 {
drawingElement?.smoothLinesPart1()
path.moveToPoint(drawingElement!.smoothLinesMoveTo())
path.addQuadCurveToPoint(drawingElement!.smoothLinesQuadCurve().0, controlPoint: drawingElement!.smoothLinesQuadCurve().1)
self.setNeedsDisplay()
drawingElement?.smoothLinesReorderArray()
ctr = 1
}
var pointsArray : [CGPoint] = drawingElement!.getPoints()
var calcArray = drawingElement?.getCalculatedPoints(pointsArray)
let sortedCalcArray = sorted(calcArray!) { $0.0 < $1.0 }
if pointsArray.count > 1 {
for (pIndex, path) in sortedCalcArray {
paths[pIndex] = UIBezierPath()
for var i = 0; i < path.count; i++ {
paths[pIndex]!.moveToPoint(path[i])
if(i > 0) {
paths[pIndex]!.addLineToPoint(path[i-1])
}
self.setNeedsDisplay()
}
}
}
override func drawRect(rect: CGRect) {
for (index, path) in paths {
path.lineCapStyle = kCGLineCapRound
path.lineWidth = lineWidth
color.setStroke()
path.stroke()
}
color.setStroke()
incrementalImage.drawInRect(rect)
}
}
I have a feeling that either 1) The iPhone does like drawing 4 or more paths within a single view at a time, or 2) the performance is degraded because of the number of loops that are running each time the user moves their finger. Here is what a similar line looks like with the above new code:
So after all of that I am wondering if anyone would be able to shed some light on why the new code draws so differently or what a better approach may be.
Thanks
So after some trial and error I kept most of the code above, the only notable difference was that I constructed 4 separate UIBezierPaths and eliminated the for loop nested in the other for loop. That seemed to be causing the issue

How to add coin score so player accumulates more and more over time, swift

I created a score and high score label. The score gets 1 pint each time the player hits a coin, the high score gets updated every time the score is higher then the high score. Now, besides this I want to change the function of the score. I don't want it to start from 0 each time the game restarts, I want it to start at the last amount the player won during the previous game. So pretty much just a score label that keeps accumulating more and more points over time even when the player shuts down the app.
This is my current code with the normal score that starts from 0 each time the player starts the game and a highscore that gets updated each time the score reaches a higher amount then the high score.
let highScoreLabel = SKLabelNode()
var score: Int {
switch (self) {
case coin: return 1
default: return 0
}
}
func playerScoreUpdate() {
playerScorelabel.text = "Score: \(playerScore)"
}
func saveHighScore(high:Int) {
NSUserDefaults.standardUserDefaults().setInteger(high, forKey: "highscore")
}
func highScore() -> Int {
return NSUserDefaults.standardUserDefaults().integerForKey("highscore")
}
func resetHighScore() {
NSUserDefaults.standardUserDefaults().removeObjectForKey("highscore")
}
func didBeginContact(contact: SKPhysicsContact) {
var firstBody = SKPhysicsBody()
var secondBody = SKPhysicsBody()
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if (firstBody.categoryBitMask & UInt32(shipCategory)) != 0 && (secondBody.categoryBitMask & UInt32(obstacleCategory)) != 0 {
ship.removeFromParent()
let reveal = SKTransition.flipHorizontalWithDuration(0.5)
let scene = GameOverScene(size: self.size)
self.view?.presentScene(scene, transition: reveal)
}
if (firstBody.categoryBitMask & UInt32(shipCategory)) != 0 && (secondBody.categoryBitMask & UInt32(coinCategory)) != 0 {
secondBody.node?.removeFromParent() // Changed line.
playerScore = playerScore + 1
playerScoreUpdate()
}
if (firstBody.categoryBitMask & UInt32(shipCategory)) != 0 && (secondBody.categoryBitMask & UInt32(diamondCategory)) != 0 {
secondBody.node?.removeFromParent() // Changed line.
}
//CHANGE TO YOU WON SCENE
//CHECK TO SEE IF COINS ARE 10, THEN YOU WON
if playerScore == 30 {
let reveal = SKTransition.flipHorizontalWithDuration(0.5)
let scene = GameWonScene(size: self.size)
self.view?.presentScene(scene, transition: reveal)
}
if playerScore > highScore() {
saveHighScore(playerScore)
println("New Highscore = " + highScore().description)
highScoreLabel.text = "HighScore: \(highScore().description)"
} else {
println("HighScore = " + highScore().description ) // "HighScore = 100"
}
}
In your didMoveToView method, you can just set the value of your highscoreLabel to the value of your highScore() method:
playerScore = highScore()
Then you should be able to start at the last highScore. You shouldn't have to change something else, because the other parts of your code should do their parts well.

Resources