What is the nicest and cleanest way to translate this Objective-C for-loop to Swift 3? What is best practice? Should I use a while loop?
NSInteger pageHeight = 792;
NSInteger pageWidth = 612;
for (int page=0; pageHeight * page < scrollView.frame.size.height; page++) {
// execute some code
}
I tried to work with something like this
for i in pageHeight * i < scrollView.frame.size.height {
// execute some code
}
but I cannot reuse the variable i since it cannot be used. Compiler gives the error: unresolved identifier 'i'.
EDIT: one of the reasons I asked the question was because I wanted to specifically know what the best practice is and to avoid creating an additional variable before the loop code.
You should use a while loop:
let pageHeight = 792
let pageWidth = 612
var page = 0
while pageHeight * page < Int(scrollView.frame.size.height) {
// execute some code
page += 1
}
Assuming you don't change any of the loop-related variables inside the loop, you can do it this way:
let pageHeight: Int = 792
let pageWidth: Int = 612
let pageCount = Int(scrollView.frame.size.height) / pageHeight
for page in 0..<pageCount {
}
As another option, here's a recursive/functional option:
func setupPages(maxHeight: CGFloat, pageHeight: CGFloat, pageNumber: Int = 0) {
guard (pageHeight * CGFloat(pageNumber)) < maxHeight else {
return
}
// do what you need to do
setupPages(maxHeight: maxHeight, pageHeight: pageHeight, pageNumber: pageNumber + 1)
}
Then in your code you can just simple call:
setupPages(maxHeight: scrollView.frame.height, pageHeight: CGFloat(pageWidth))
Related
I am trying to convert an old simulation from Flash to createjs with animate cc. The only code you have is to rotate a piece but I can not get it to work. This code not work:
function spinit()
{
var ang = myangle * Math.PI / 180;
this.pin1.x = disk1.x + R * Math.cos(ang);
this.pin1.y = disk1.y + R * Math.sin(ang);
this.yoke1.x = pin1.x;
this.disk1.rotate = myangle;
this.myangle = myangle + 1;
if (myangle > 360)
{
myangle = 0;
}
}
var myangle = 0;
var R = 90;
setInterval(spinit, 5);
Chances are good this is a scope issue. Your spinit method is being called anonymously, so it won't have access to any of your frame content referenced with this. You can get around this by scoping your method, and binding your setInterval call.
this.spinit = function() // 1. scope the function
{
var ang = myangle * Math.PI / 180;
this.pin1.x = disk1.x + R * Math.cos(ang);
// etc
setInterval(spinit.bind(this), 5); // 2. bind this so it calls in the right scope.
}
Make sure to call this.spinit().
Hope that helps!
I've been racking my brain and searching here and all over to try to find out how to generate a random position on screen to spawn a circle. I'm hoping someone here can help me because I'm completely stumped. Basically, I'm trying to create a shape that always spawns in a random spot on screen when the user touches.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenHeight = screenSize.height
let screenWidth = screenSize.width
let currentBall = SKShapeNode(circleOfRadius: 100)
currentBall.position = CGPointMake(CGFloat(arc4random_uniform(UInt32(Float(screenWidth)))), CGFloat(arc4random_uniform(UInt32(Float(screenHeight)))))
self.removeAllChildren()
self.addChild(currentBall)
}
If you all need more of my code, there really isn't any more. But thank you for whatever help you can give! (Just to reiterate, this code kind of works... But a majority of the spawned balls seem to spawn offscreen)
The problem there is that you scene is bigger than your screen bounds
let viewMidX = view!.bounds.midX
let viewMidY = view!.bounds.midY
print(viewMidX)
print(viewMidY)
let sceneHeight = view!.scene!.frame.height
let sceneWidth = view!.scene!.frame.width
print(sceneWidth)
print(sceneHeight)
let currentBall = SKShapeNode(circleOfRadius: 100)
currentBall.fillColor = .green
let x = view!.scene!.frame.midX - viewMidX + CGFloat(arc4random_uniform(UInt32(viewMidX*2)))
let y = view!.scene!.frame.midY - viewMidY + CGFloat(arc4random_uniform(UInt32(viewMidY*2)))
print(x)
print(y)
currentBall.position = CGPoint(x: x, y: y)
view?.scene?.addChild(currentBall)
self.removeAllChildren()
self.addChild(currentBall)
First: Determine the area that will be valid. It might not be the frame of the superview because perhaps the ball (let's call it ballView) might be cut off. The area will likely be (in pseudocode):
CGSize( Width of the superview - width of ballView , Height of the superview - height of ballView)
Once you have a view of that size, just place it on screen with the origin 0, 0.
Secondly: Now you have a range of valid coordinates. Just use a random function (like the one you are using) to select one of them.
Create a swift file with the following:
extension Int
{
static func random(range: Range<Int>) -> Int
{
var offset = 0
if range.startIndex < 0 // allow negative ranges
{
offset = abs(range.startIndex)
}
let mini = UInt32(range.startIndex + offset)
let maxi = UInt32(range.endIndex + offset)
return Int(mini + arc4random_uniform(maxi - mini)) - offset
}
}
And now you can specify a random number as follows:
Int.random(1...1000) //generate a random number integer somewhere from 1 to 1000.
You can generate the values for the x and y coordinates now using this function.
Given the following random generators:
public extension CGFloat {
public static var random: CGFloat { return CGFloat(arc4random()) / CGFloat(UInt32.max) }
public static func random(between x: CGFloat, and y: CGFloat) -> CGFloat {
let (start, end) = x < y ? (x, y) : (y, x)
return start + CGFloat.random * (end - start)
}
}
public extension CGRect {
public var randomPoint: CGPoint {
var point = CGPoint()
point.x = CGFloat.random(between: origin.x, and: origin.x + width)
point.y = CGFloat.random(between: origin.y, and: origin.y + height)
return point
}
}
You can paste the following into a playground:
import XCPlayground
import SpriteKit
let view = SKView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
XCPShowView("game", view)
let scene = SKScene(size: view.frame.size)
view.presentScene(scene)
let wait = SKAction.waitForDuration(0.5)
let popIn = SKAction.scaleTo(1, duration: 0.25)
let popOut = SKAction.scaleTo(0, duration: 0.25)
let remove = SKAction.removeFromParent()
let popInAndOut = SKAction.sequence([popIn, wait, popOut, remove])
let addBall = SKAction.runBlock { [unowned scene] in
let ballRadius: CGFloat = 25
let ball = SKShapeNode(circleOfRadius: ballRadius)
var popInArea = scene.frame
popInArea.inset(dx: ballRadius, dy: ballRadius)
ball.position = popInArea.randomPoint
ball.xScale = 0
ball.yScale = 0
ball.runAction(popInAndOut)
scene.addChild(ball)
}
scene.runAction(SKAction.repeatActionForever(SKAction.sequence([addBall, wait])))
(Just make sure to also paste in the random generators, too, or to copy them to the playground's Sources, as well as to open the assistant editor so you can see the animation.)
I wanted to get an image of the screen, ignoring my app's window.
Found a code example in Objective-C and tried to convert it to Swift.
Objective-C snipped:
// Get onscreen windows
CGWindowID windowIDToExcude = (CGWindowID)[myNSWindow windowNumber];
CFArrayRef onScreenWindows = CGWindowListCreate(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
CFMutableArrayRef finalList = CFArrayCreateMutableCopy(NULL, 0, onScreenWindows);
for (long i = CFArrayGetCount(finalList) - 1; i >= 0; i--) {
CGWindowID window = (CGWindowID)(uintptr_t)CFArrayGetValueAtIndex(finalList, i);
if (window == windowIDToExcude)
CFArrayRemoveValueAtIndex(finalList, i);
}
// Get the composite image
CGImageRef ref = CGWindowListCreateImageFromArray(myRectToGrab, finalList, kCGWindowListOptionAll);
My version in Swift (where I managed to get so far):
// Get onscreen windows
let windowIDToExcude = myNSWindow.windowNumber!
let onScreenWindows = CGWindowListCreate(kCGWindowListOptionOnScreenOnly, kCGNullWindowID)
let finalList = CFArrayCreateMutableCopy(nil, 0, onScreenWindows)
for var i = CFArrayGetCount(finalList) - 1; i >= 0; i-=1 {
var window: CGWindowID = (uintptr_t(CFArrayGetValueAtIndex(finalList, i)) as! CGWindowID)
if window == windowIDToExcude {
CFArrayRemoveValueAtIndex(finalList, i)
}
}
// Get the composite image
var ref = CGWindowListCreateImageFromArray(myRectToGrab, finalList, kCGWindowListOptionAll)
But it does not work in swift 2.0 and I have no idea why.
Particularly this line can't be compiled:
CGWindowListCreate(kCGWindowListOptionOnScreenOnly, kCGNullWindowID)
Apparently there is no such thing as CGWindowListCreate, kCGWindowListOptionOnScreenOnly and kCGNullWindowID anymore.
Can you try this:
let imageRef = CGWindowListCreateImage(self.view.frame, CGWindowListOption.OptionOnScreenBelowWindow, CGWindowID(self.view.window!.windowNumber), CGWindowImageOption.Default)
let image = NSImage(CGImage: imageRef!, size: self.view.frame.size)
self.imageView.image = image
That does the trick for me.
Pulling my hair out with this one, hope its not something silly. Below is a snippet of code from a program. When leftwalker.x == 150 he should gotoAndPlay the standstill animation but it only plays the first frame, the previous animation runs fine. Any ideas?
var data =
{
images: ["images/ste_basic_wand.png"],
frames: {width:64, height:64},
animations:
{
// start, end, next, speed
walkright: [143,151,"walkright",1.18],
walkleft: [118,125,"walkleft",1.18],
stand:[39,45,"stand",0.08],
standstill:[26,27, "standstill", 1.2]
}
};
var spriteSheet = new createjs.SpriteSheet(data);
leftwalker = new createjs.Sprite(spriteSheet);
leftwalker.name = "lefty";
leftwalker.framerate = 30;
leftwalker.x = 100;
leftwalker.y = 100;
leftwalker.currentFrame = 0;
leftwalker.scaleY = leftwalker.scaleX = 2;
leftwalker.gotoAndPlay("walkright");
stage.addChild(leftwalker);
createjs.Ticker.setFPS(10);
createjs.Ticker.addEventListener("tick", tick);
}
function tick(event) {
if(container.x < 150)
{
container.x += 5;
}
if(leftwalker.x < 150)
{
leftwalker.x += 2;
}
if(leftwalker.x == 150)
{
leftwalker.gotoAndPlay("standstill");
}
// if (circle.x > stage.canvas.width) { circle.x = 0; }
stage.update(event); // important!!
}
The reason this happens is because you are calling gotoAndPlay("standstill") during the tick. Once you reach 150, your sprite stops moving, so it is perpetually at x=150. This means each tick will tell it to gotoAndPlay the same frame, resulting it in being "stuck".
Figured out a way around it, still not sure why but easaljs didn't like the code
if(leftwalker.x == 150)
{
leftwalker.gotoAndPlay("standstill");
}
When I change it so the char isn't stuck on point 150 (move him to 151) the animation begins. I also slowed the animation on the standing still down to make it seem more real but this isn't related to the fix I didn't post this code.
if(leftwalker.x == 150)
{
leftwalker.gotoAndPlay("standstill");
if(leftwalker.x < 180)
{
leftwalker.x += 1;
}
}
I'm trying to convert this example here:
https://github.com/NilStack/NKWatchChart
to swift2-code. I'm having problems with exactly this part:
data01.getData = ^(NSUInteger index) {
CGFloat yValue = [data01Array[index] floatValue];
return [NKLineChartDataItem dataItemWithY:yValue];
};
getData is defined as follows:
public class NKLineChartData : NSObject {
public var color: UIColor!
public var alpha: CGFloat
public var itemCount: UInt
public var getData: LCLineChartDataGetter!
The LCLineChartDataGetter is a type alias:
public typealias LCLineChartDataGetter = (UInt) -> NKLineChartDataItem!
I'm pretty new to objective-c and swift, so maybe someone can point me to how the assignment can be done using swift!
I found the solution myself. The equivalent in Swift is:
data01.getData = {(index : UInt) -> NKLineChartDataItem in
let yValue : CGFloat = CGFloat(data01Array[Int(index)] as! NSNumber)
return NKLineChartDataItem.init(y: yValue)
}
Maybe someone has a better/shorter solution but the above seems to work ;)
The thing being assigned to getData in Obj-C is a block that takes an integer parameter and returns a NKLineChartDataItem. (Check something like http://goshdarnblocksyntax.com if you need help remembering these.)
In Swift, the formal syntax for the corresponding closure looks like this:
data01.getData = { (index: UInt) -> NKLineChartDataItem! in
// body
}
But you can also use type inference to shorten it:
data01.getData = { index in
// body
}
Looking at more of the example from the NKWatchChart readme you linked to:
NSArray * data01Array = #[#60.1, #160.1, #126.4, #0.0, #186.2, #127.2, #176.2];
NKLineChartData *data01 = [NKLineChartData new];
data01.color = NKGreen;
data01.alpha = 0.9f;
data01.itemCount = data01Array.count;
data01.inflexionPointStyle = NKLineChartPointStyleTriangle;
data01.getData = ^(NSUInteger index) {
CGFloat yValue = [data01Array[index] floatValue];
return [NKLineChartDataItem dataItemWithY:yValue];
};
You can probably shorten this even more through native Swift types:
let data01Array: [CGFloat] = [60.1, 160.1, 126.4, 0.0, 186.2, 127.2, 176.2]
let data01 = NKLineChartData()
data01.color = NKGreen
data01.alpha = 0.9
data01.itemCount = data01Array.count
data01.inflexionPointStyle = .Triangle;
data01.getData = { NKLineChartDataItem(y: data01Array[$0]) }
You don't need to convert swift to objective-c to make it run in your project. Just drag the swift into your project and Xcode will automatically generate a bridging header for you