View unanswered posts | View active topics
It is currently Sun May 04, 2025 1:42 pm
Dipping the toe in - Swift, Xcode, adaptive layouts
Author |
Message |
paulzolo
What's a life?
Joined: Thu Apr 23, 2009 6:27 pm Posts: 12251
|

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.
|
Mon Nov 03, 2014 1:38 pm |
|
 |
Fogmeister
I haven't seen my friends in so long
Joined: Thu Apr 23, 2009 7:35 pm Posts: 6580 Location: Getting there
|

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.
|
Mon Nov 03, 2014 2:09 pm |
|
 |
paulzolo
What's a life?
Joined: Thu Apr 23, 2009 6:27 pm Posts: 12251
|
Thanks - I'll have to dig into CALayer.
|
Mon Nov 03, 2014 5:47 pm |
|
 |
paulzolo
What's a life?
Joined: Thu Apr 23, 2009 6:27 pm Posts: 12251
|

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-swiftI 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: 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: 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.
|
Tue Nov 04, 2014 12:42 pm |
|
 |
EddArmitage
I haven't seen my friends in so long
Joined: Thu Apr 23, 2009 9:40 pm Posts: 5288 Location: ln -s /London ~
|
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.
|
Tue Nov 04, 2014 3:38 pm |
|
 |
paulzolo
What's a life?
Joined: Thu Apr 23, 2009 6:27 pm Posts: 12251
|
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.
|
Tue Nov 04, 2014 4:09 pm |
|
 |
paulzolo
What's a life?
Joined: Thu Apr 23, 2009 6:27 pm Posts: 12251
|

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: (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 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?
|
Tue Nov 04, 2014 4:31 pm |
|
 |
paulzolo
What's a life?
Joined: Thu Apr 23, 2009 6:27 pm Posts: 12251
|
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.
|
Wed Nov 05, 2014 10:31 am |
|
 |
Fogmeister
I haven't seen my friends in so long
Joined: Thu Apr 23, 2009 7:35 pm Posts: 6580 Location: Getting there
|
Working with layers is a lot like working with UIViews. To add a layer to a view you add it as a sublayer... etc... Assuming that self is the clockView.
|
Wed Nov 05, 2014 5:18 pm |
|
 |
paulzolo
What's a life?
Joined: Thu Apr 23, 2009 6:27 pm Posts: 12251
|

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: 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: 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: I’ve got these line of code which, I assume, adds the Clock object to the theCompleteClock At this point, I know I am making an error because I am defining Clock like this: 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.
|
Thu Nov 06, 2014 9:19 am |
|
 |
Fogmeister
I haven't seen my friends in so long
Joined: Thu Apr 23, 2009 7:35 pm Posts: 6580 Location: Getting there
|
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.
|
Thu Nov 06, 2014 12:49 pm |
|
 |
paulzolo
What's a life?
Joined: Thu Apr 23, 2009 6:27 pm Posts: 12251
|
*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.
|
Thu Nov 06, 2014 4:45 pm |
|
 |
paulzolo
What's a life?
Joined: Thu Apr 23, 2009 6:27 pm Posts: 12251
|
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): 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: 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.
|
Fri Nov 07, 2014 10:20 am |
|
 |
Fogmeister
I haven't seen my friends in so long
Joined: Thu Apr 23, 2009 7:35 pm Posts: 6580 Location: Getting there
|

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... Now from your view controller you can create your timer for the time and then somewhere you can do... 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?
|
Fri Nov 07, 2014 2:26 pm |
|
 |
paulzolo
What's a life?
Joined: Thu Apr 23, 2009 6:27 pm Posts: 12251
|
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.
|
Fri Nov 07, 2014 5:51 pm |
|
|
Who is online |
Users browsing this forum: No registered users and 2 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
|
|