Reply to topic  [ 79 posts ]  Go to page 1, 2, 3, 4, 5, 6  Next
Dipping the toe in - Swift, Xcode, adaptive layouts 
Author Message
What's a life?
User avatar

Joined: Thu Apr 23, 2009 6:27 pm
Posts: 12251
Reply with quote
One of the side effects of installing Yosemite is that I get to try out Apple’s new language, Swift. For me (who spends most of his “programming” time in JavaScript or PHP), it feels quite familiar. I had also made serious inroads in a JavaScript/HTML/CSS app, called Hype, into a metric clock (10 hours/100 minutes per hour). You can see it here:
http://www.worldofpaul.com/metric_clock/
(and it should work as long as your browser supports rotating things)

So, I decided to try and get this kind of thing working as an iOS app. I needed a project, and this seems as good a place to start. I am, so far, doing well. The app as it stands looks pretty much like the web version above in the iOS simulator when running it on an iPhone 6. However, I am trying (and failing) to get it to look right on an iPad.

I’m using the layout tools in Xcode, and have fiddled around with the Main.storyboard file in Xcode - and have managed to get things centred - but this is about as far as my understanding of things goes.

What I want is for the size of the items in the app (the face of the clock, the hands, etc.) to get bigger on larger displays, but I can’t see how I can do that. I know I’ll need to provide larger assets for these things (this is the easy bit for me), but scaling, possibly repositioning (everything is dead centre, but Art requires that everything be a little higher than dead centre, especially in portrait) is something I don’t quite get.

For example, let’s say I want the clock face (which is a circle) to be 95% of the device’s shortest measurement (i.e. if an iPad is held horizontally, it would be the width), I can’t see how I can set that up by editing the Main.storyboard file Xcode. Similar, but (I expect) more complex, would be the size of the hands which would be proportional to the size of the face, and positioned so their centre of rotation would be the centre of the clock face. I would also need to substitute larger versions of the images for the iPad family of devices.

It’s all a bit bewildering - but something I’m wanting to see what I can do with. Any suggestions or pointers on what I should be looking at here?

Thanks. I know, RTFM, but at times I find the Manual to be written for people with more knowledge than I have.

_________________
All the best,
Paul
brataccas wrote:
your posts are just combo chains of funny win

I’m on Twitter, tweeting away... My Photos Random Avatar Explanation


Mon Nov 03, 2014 1:38 pm
Profile
I haven't seen my friends in so long
User avatar

Joined: Thu Apr 23, 2009 7:35 pm
Posts: 6580
Location: Getting there
Reply with quote
I'd build the "clock" as a single entity. So you should only need to add one thing (the clock) to the storyboard rather than the clock and the hands etc...

I'd make it a subclass of UIView. Then you can do things like adding the images for the face and hands to it from there. Possibly using CALayer you can create the layers for the face and the hands when the view is loaded.

As for the size, shape and position they're a bit different. With the "95%" thing it's more tricky. You'd be better having it something like...

1. The clock will always be square (well, a circle, but square for layout purposes).
2. It will always have at least 20 points space from each edge.
3. It will always be placed centrally in the view.

(For 3 you could make it slightly above the centre but you'd have to code the amount if it changes per device).

1. You can constrain the Aspect Ratio using AutoLayout. Add an aspect ratio and set it to "1" (i.e. width = height * 1)
2. Add constraints to each edge. Change them to (>= 20).
3. Add a centrally aligned constraint both horizontally and vertically.

I think that should be enough to position it and size it.

_________________
Oliver Foggin - iPhone Dev

JJW009 wrote:
The count will go up until they stop counting. That's the way counting works.


Doodle Sub!
Game Of Life

Image Image


Mon Nov 03, 2014 2:09 pm
Profile WWW
What's a life?
User avatar

Joined: Thu Apr 23, 2009 6:27 pm
Posts: 12251
Reply with quote
Thanks - I'll have to dig into CALayer.

_________________
All the best,
Paul
brataccas wrote:
your posts are just combo chains of funny win

I’m on Twitter, tweeting away... My Photos Random Avatar Explanation


Mon Nov 03, 2014 5:47 pm
Profile
What's a life?
User avatar

Joined: Thu Apr 23, 2009 6:27 pm
Posts: 12251
Reply with quote
Well, I’m stumped. I’ve been digging around looking for CALayer stuff, and I found some information, and I’ve been following/adapting code from here to do what I want.
http://www.raywenderlich.com/76433/how- ... trol-swift

I do have some blocks on the screen screen that will, eventually, represent the face and hands. OK, so far, so good, and if I follow what I’ve done right, the face and hands are all parts of an object called clock, so they should scale when I eventually get round to working that bit out.

What I am trying to do is to get the face image to display correctly in the clock face object (yes I know - but I’m your typical designer - I want to get things looking nice as I go along, and anyway, I can work on other stuff pending sorting this out). I can get it to fill a solid colour, BUT I can’t get it to put the image in. I’ve tried a number of ways to achieve this, but the compiler fails every time.

This is my ClockFace.swift code:

Code:
import UIKit
import QuartzCore

class ClockFaceLayer: CALayer {
    weak var clockFace: Clock?
    var clockFaceImage = UIImage(named:"Clock Face 320")!
   
    override func drawInContext(context: CGContext!) {
        if let myClockFace = clockFace {
            // Clip
            let cornerRadius = bounds.height * myClockFace.curvaceousness / 2.0
            let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
            CGContextAddPath(context, path.CGPath)

            CGContextDrawImage(context, CGRectMake(0, 0, bounds.width, bounds.height), clockFaceImage)
           
            CGContextAddPath(context, path.CGPath)
            CGContextFillPath(context)


        }
    }
   
}


The compiler fails at CGContextDrawImage(context, CGRectMake(0, 0, bounds.width, bounds.height), clockFaceImage) - and all my Google efforts so far have told me that this SHOULD work. It doesn’t like the CGRectMake bit - the error being:
Code:
/ClockFace.swift:25:52: Cannot convert the expression's type '(CGContext!, CGRect, @lvalue UIImage)' to type 'IntegerLiteralConvertible'


So close, I expect, yet so far (or I am really barking up the wrong tree). Some of the stuff in there (such as curvaceousness) are part of the example code I have been looking, and will go when I’ve got things working more comfortably.

_________________
All the best,
Paul
brataccas wrote:
your posts are just combo chains of funny win

I’m on Twitter, tweeting away... My Photos Random Avatar Explanation


Tue Nov 04, 2014 12:42 pm
Profile
I haven't seen my friends in so long
User avatar

Joined: Thu Apr 23, 2009 9:40 pm
Posts: 5288
Location: ln -s /London ~
Reply with quote
I don't have a compiler to hand and I've never developed in Swift or Objective-C, but at a guess it's fine with the CGRect param, it's the final param - you're passing a UIImage and it wants a CGImage. I think clockFaceImage.CGImage or similar should help.

_________________
timark_uk wrote:
Gay sex is better than no sex

timark_uk wrote:
Edward Armitage is Awesome. Yes, that's right. Awesome with a A.


Tue Nov 04, 2014 3:38 pm
Profile
What's a life?
User avatar

Joined: Thu Apr 23, 2009 6:27 pm
Posts: 12251
Reply with quote
EddArmitage wrote:
I don't have a compiler to hand and I've never developed in Swift or Objective-C, but at a guess it's fine with the CGRect param, it's the final param - you're passing a UIImage and it wants a CGImage. I think clockFaceImage.CGImage or similar should help.


Ah, thanks - that’s it. I had to do some more Googling, but I finally came up with a display of the clock face!

Code:
class ClockFaceLayer: CALayer {
    weak var clockFace: Clock?
    var clockFaceImage:CGImageRef = UIImage(named: "Clock Face 320")!.CGImage
   
    override func drawInContext(context: CGContext!) {
        if let myClockFace = clockFace {           
            // Image is flipped, so unflip it (this is docmented as a problem)
            CGContextTranslateCTM(context, 0, bounds.height)
            CGContextScaleCTM(context, 1.0, -1.0)

            CGContextDrawImage(context, CGRectMake(0, 0, bounds.width, bounds.height), clockFaceImage)
           
        }
    }
}


A documented problem is that in this kind of use, the image is displayed upside down, so you have to transform it to correct. Apparently, Apple’s demos do this. So this is starting to work nicely. Now to draw the hands. Eventually, I’ll be able to animate them.

There will be more questions, believe me.

_________________
All the best,
Paul
brataccas wrote:
your posts are just combo chains of funny win

I’m on Twitter, tweeting away... My Photos Random Avatar Explanation


Tue Nov 04, 2014 4:09 pm
Profile
What's a life?
User avatar

Joined: Thu Apr 23, 2009 6:27 pm
Posts: 12251
Reply with quote
Actually, before I go down a particular rabbit hole I’d better ask if I am taking the right approach for the whole scaling things to fit on the screen front.

What I am doing is this:

1 - Making the clock face the right size for the screen:
Code:
theClockFace.contentsScale = UIScreen.mainScreen().scale

(This works - fits nicely in various devices in the simulator)

2 - Grabbing the dimensions of the clock face - this gives the dimensions of the object on the screen, not the pixel dimensions of the image used

3 - Calculating the relative sizes of the hands using the dimensions returned in step 2, and applying them along these lines
Code:
theSecondHand.frame = CGRect(x: secondHandcentralX, y: secondHandcentralY,width: secondHandWidth, height: secondHandHeight)


I expect this is very wrong, especially step 3. If I understand what Fogmeister says when he says “I'd make it a subclass of UIView. Then you can do things like adding the images for the face and hands to it from there.” I take it that I should be able to somehow say to XCode - here’s my model of the clock, scale it yourself. I’m not sure how to use AutoView, and it’s not obvious how I add anything like the CALayers to it in the GUI, or do I have to add a UIView object, and somehow put the CALayers in there in the code? I expect that this is how it’s meant to be done, as then guess I can give dimensions within the UIView, and the device will scale accordingly. Is that right?

_________________
All the best,
Paul
brataccas wrote:
your posts are just combo chains of funny win

I’m on Twitter, tweeting away... My Photos Random Avatar Explanation


Tue Nov 04, 2014 4:31 pm
Profile
What's a life?
User avatar

Joined: Thu Apr 23, 2009 6:27 pm
Posts: 12251
Reply with quote
Well, I have my UIView, but I can’t for the life of me find how to draw the CALayers into it. To make matters worse, there seems to be no info on how to do this in Swift out there, so I’ve hit a wall. Everything I try throws up errors when I try to build it.

_________________
All the best,
Paul
brataccas wrote:
your posts are just combo chains of funny win

I’m on Twitter, tweeting away... My Photos Random Avatar Explanation


Wed Nov 05, 2014 10:31 am
Profile
I haven't seen my friends in so long
User avatar

Joined: Thu Apr 23, 2009 7:35 pm
Posts: 6580
Location: Getting there
Reply with quote
Working with layers is a lot like working with UIViews.

To add a layer to a view you add it as a sublayer...

Code:
self.layer.addSublayer(faceLayer)
self.layer.addSublayer(handLayer)

etc...

Assuming that self is the clockView.

_________________
Oliver Foggin - iPhone Dev

JJW009 wrote:
The count will go up until they stop counting. That's the way counting works.


Doodle Sub!
Game Of Life

Image Image


Wed Nov 05, 2014 5:18 pm
Profile WWW
What's a life?
User avatar

Joined: Thu Apr 23, 2009 6:27 pm
Posts: 12251
Reply with quote
Fogmeister wrote:
Working with layers is a lot like working with UIViews.

To add a layer to a view you add it as a sublayer...

Code:
self.layer.addSublayer(faceLayer)
self.layer.addSublayer(handLayer)

etc...

Assuming that self is the clockView.


I am doing this, but I am not sure if I am doing it properly. Things aren’t quite working out as they are. Instead of the clock being drawn in the UIView I have set aside for it, they seem to be being drawn in the main view. Things are being drawn (good) but not in the right place (bad).

I’m going to have to describe a little about what I am doing, with my obvious limitations in play.

In Main.storyboard, I have the following hierachy:

Code:
• View controller
•• View (the main screen)
••• View ( a UIView called theCompleteClock )


What I am expecting to happen (but I can’t actually tell because I can’t seem to be able to add CALayers using the UI visual designer - I wish I could - or can I - it would make this a lot simper) is that after all the CALayers have been added, the hierachy of objects should look like this:

Code:
• View controller
••• View (the main screen)
••••• View ( a UIView called theCompleteClock )
••••••• ClockFace
••••••• SecondHand
••••••• HourHand
••••••• MinuteHand


But that does not seem to tally with what I get when I run the simulator. I’m expecting the clock to be inside theCompleteClock (which, for now, is bottom left abd about 300px wide so I can tell if things are happening as they should). I expect that things like this are happening:

Code:
• View controller
••• View (the main screen)
••••• ClockFace
••••• SecondHand
••••• HourHand
••••• MinuteHand
••••• View ( a UIView called theCompleteClock )


I’ve got these line of code which, I assume, adds the Clock object to the theCompleteClock
Code:
let myClock:Clock = Clock(frame: CGRectZero) // I expect this may be wrong
theCompleteClock.addSubview(myClock)


At this point, I know I am making an error because I am defining Clock like this:

Code:
class Clock: UIControl {
...
}


and I expect, like the components (hands, etc), it should be a CALayer, but I get compile errors if I change Clock’s type to CALayer, and a whole lot of stuff inside that class breaks. I tend to change a line or so at a time, see what happens, and if it breaks, go back one step.

What I am expecting to happen is that I draw the clock in the UIView called theCompleteClock, and create all the elements inside that as CALayers, and that auto scales itself based on settings/screen size etc..

I have been trying to find some simple examples of how to sue CALayers in Swift, but all the documentation seems to be in Obj-C, and a few years old, which isn’t very helpful, OR the examples are buried deep in big, lengthy “lets walk you through writing a game” type tutorials.

_________________
All the best,
Paul
brataccas wrote:
your posts are just combo chains of funny win

I’m on Twitter, tweeting away... My Photos Random Avatar Explanation


Thu Nov 06, 2014 9:19 am
Profile
I haven't seen my friends in so long
User avatar

Joined: Thu Apr 23, 2009 7:35 pm
Posts: 6580
Location: Getting there
Reply with quote
Ah yeah, you can't add the layers from the Interface Builder.

You have to add them in code.

So, in the method awakeFromNib (this is called when a view is loaded from the interface builder) you create the layers and add them to the clock view.

You might also want to override the method layoutSubviews. This is called when the view changes shape or size. You can use this to make sure the layers are in the correct position to display properly.

_________________
Oliver Foggin - iPhone Dev

JJW009 wrote:
The count will go up until they stop counting. That's the way counting works.


Doodle Sub!
Game Of Life

Image Image


Thu Nov 06, 2014 12:49 pm
Profile WWW
What's a life?
User avatar

Joined: Thu Apr 23, 2009 6:27 pm
Posts: 12251
Reply with quote
Fogmeister wrote:
Ah yeah, you can't add the layers from the Interface Builder.

You have to add them in code.

So, in the method awakeFromNib (this is called when a view is loaded from the interface builder) you create the layers and add them to the clock view.

You might also want to override the method layoutSubviews. This is called when the view changes shape or size. You can use this to make sure the layers are in the correct position to display properly.


*Scratches Head*

I think I may have it now. I’ve got a UIView, and into that I’m drawing a couple of CALayers which have the face and one of the hands. This is going to need a bit more work to get everything in that view, but it looks promising (which is what I thought the last couple of times....)

I have found that the code I’m using now is significantly smaller than what I had before - hopefully an indication that I’ve actually got somewhere.

The fun after this will be scaling to fit in windows (right now, everything is based in a 320x320 square) and moving the hands.

_________________
All the best,
Paul
brataccas wrote:
your posts are just combo chains of funny win

I’m on Twitter, tweeting away... My Photos Random Avatar Explanation


Thu Nov 06, 2014 4:45 pm
Profile
What's a life?
User avatar

Joined: Thu Apr 23, 2009 6:27 pm
Posts: 12251
Reply with quote
OK - so, as I said, I’ve got some CALayers drawing in a UIView now, and the thing is drawing in the Simulator as I expect it to.

My next task is to look at rotating the hand (so far, I’ve only got one - but eventually there will be three).

So, in a file called ClockView.swift, I have something like this (I’ve lopped out stuff that I don‘t think is necessary for my next question):

Code:
class ClockView: UIView {
    var secondHandLayer = CALayer()
    var faceLayer = CALayer()

    println("ClockView")

    override func awakeFromNib() {
        super.awakeFromNib()
       
        let clockView = ClockView()
       
        println("Ready to go")
       
    }

    override func drawRect(rect: CGRect) {
        // clock face drawing here

        secondHandLayer.frame = CGRect(x: 10, y: 10,  width: 13, height: 150)
        // second hand image and placing reference here
        self.layer.insertSublayer(secondHandLayer, atIndex: 1)
        println("Calling rotate second hand")
        self.rotateSecondHand()

    }

    func rotateSecondHand() {
        println("Rotating second hand")
        secondHandLayer.transform = CATransform3DMakeRotation(3.14/3 0.0, 0.0, 1.0)
    }
}



So far, so good - when the Simulator opens, the clockface draws, and the hand is rotated to a specific point.

However, the println statements tell me that things are not happening in the order I expect. The order they appear in the log are:

Quote:
Ready to go
ClockView
Calling rotate second hand
Rotating second hand


Is there a method which I can use to start things in earnest when the initial drawing has been done? I don’t want to do everything (start timing, etc, etc) in drawRect(). I don’t think that’s what it’s for.

_________________
All the best,
Paul
brataccas wrote:
your posts are just combo chains of funny win

I’m on Twitter, tweeting away... My Photos Random Avatar Explanation


Fri Nov 07, 2014 10:20 am
Profile
I haven't seen my friends in so long
User avatar

Joined: Thu Apr 23, 2009 7:35 pm
Posts: 6580
Location: Getting there
Reply with quote
OK got some questions.

What's in the init method of the ClockView class? You shouldn't need the line "let clockView = ClockView()" the view is instantiated by the storyboard file. You're just setting stuff up here (like adding layers etc...)

Also, drawRect is used when you actually want to draw on the view. So things like painting apps etc... will draw a line using drawRect and each frame it will add new bits of the line as the user draws more. Because you're using layers it means you don't need to use drawRect at all. The layers "draw" themselves wherever you put them. All you need to do is make sure they're in the right place.

What you should be doing in the awakeFromNib function is something like this...

Code:
override func awakeFromNib() {
    // always call super
    super.awakeFromNib()

    // set up the view and add the sublayers

    //create the faceLayer here...

    secondHandLayer.frame = CGRect(x:10, y:10,  width:13, height:150)
    secondHandLayer.anchorPoint = CGPointMake(0.5, 0.9) //this sets (percentage-wise) the point that the rotation will be applied from
    // in this case it would be the point (13 * 0.5, 150  0.9) = (6.5, 135) in the image.
   
    self.layer.addSublayer(secondHandLayer) // these are layered in the order they are added (first = bottom, last = top)
    // self.layer.insertSublayer(secondHandLayer, atIndex: 1)

    // the layers persist once you've added them so you only need to add them once.
    // then you can rotate, reposition, resize them and they will update on the view.
   
    println("Ready to go")
}


What I would do though is make the clock completely "dumb" in the sense that it has no idea what time it is etc...

Add a function something like this...

Code:
func setSecond(second: Int) {
    println("Setting the second hand to \(second) seconds.")

    let angle = M_PI * 2.0 / (float)second

    secondHandLayer.transform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
}


Now from your view controller you can create your timer for the time and then somewhere you can do...
Code:
// probably in a time changed method or something
self.clockView.setSecond(27)


I think using the drawRect method was throwing off the timing because what it is used for requires it to run at different times. Also, each time you would have changed the view etc... it would have added in new secondHands etc...

Hope that makes sense?

_________________
Oliver Foggin - iPhone Dev

JJW009 wrote:
The count will go up until they stop counting. That's the way counting works.


Doodle Sub!
Game Of Life

Image Image


Fri Nov 07, 2014 2:26 pm
Profile WWW
What's a life?
User avatar

Joined: Thu Apr 23, 2009 6:27 pm
Posts: 12251
Reply with quote
Oli - thanks. I think it does. I'm getting ready for a coupe,mod days away, so I won't be able to look at this at my regular screen until Monday.

It looks like I'm doing things ring and wrong at the same time - it seems that my initial work on this was kind of in the right area, but I went off track a bit. I was indeed aiming to make the clock "dumb" - but I've adding stuff in to make sure that things will happen as expected.

Thanks for your help - I've done more with Xcode the last coup,e of weeks than I ever have before, and I'm getting more interesting results.

A friend of mine is attentions the same thing, but in LiveCode. I think he wants to see how we both get on.

_________________
All the best,
Paul
brataccas wrote:
your posts are just combo chains of funny win

I’m on Twitter, tweeting away... My Photos Random Avatar Explanation


Fri Nov 07, 2014 5:51 pm
Profile
Display posts from previous:  Sort by  
Reply to topic   [ 79 posts ]  Go to page 1, 2, 3, 4, 5, 6  Next

Who is online

Users browsing this forum: No registered users and 93 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group
Designed by ST Software.