As you saw earlier, WKInterfaceController’s animateWithDuration(duration:animations:) method allows you to update properties of your interface objects inside an animation. WKInterfaceGroup is no different, and you can animate all sorts of properties, just like other interface objects. One animation property belongs to groups alone, and that is the content inset. This inset allows you to push content back from the edge of the group, and it can be useful for moving an object around the screen with more control than simple horizontal or vertical alignment.
To illustrate group animations, let’s make a simple watchOS game. Turns out, we can make one to illustrate group animation in about 100 lines of Swift! Our simple “game” will have a white ball on a green field and buttons to virtually “kick” the ball around on the screen. It’s not much, but my three-year-old loves it! In Xcode, select File → New → Project…, and then select iOS App with WatchKit App from the Watch OS section. Name it “Soccer”—or “Football” if you’re outside the United States—and click Next. Then save it. In the project that opens, head to the watch app target’s Interface.storyboard storyboard, and add two groups. You’ll use the top one for the field, so select it, and then in Xcode’s Attributes Inspector, give it a green background. You want it to mostly fill the screen, so change its width and height to Relative to Container with a value of 1. To give you room for some buttons below, give it a Height Adjustment of -44. Next, you’ll add the ball as another group inside the soccer field group. Give this one a white background. To make it round, give it a fixed width and height of 20 and a corner radius of 10. It’ll look like a circle, exactly what you want!
In the bottom group, add two buttons. One will move the ball horizontally and the other vertically. Set their widths to 50% relative to their container, and set their heights to 44 to match the offset of the field. For the left one, which will move the ball vertically, set its title to ↓ (if you’re having trouble with that character, select Edit → Emoji & Symbols to bring up an entry pane and navigate to the Bullets/Stars section). The right button will control the horizontal position of the ball, so set its title to →. The interface controller should look as shown in the figure.
Let’s head to the code. Open the InterfaceController.swift file that the Xcode template helpfully created. You can remove the existing methods, because you won’t be using them. To begin, you’ll add @IBOutlets for your interface objects:
| @IBOutlet weak var soccerBall: WKInterfaceGroup! |
| @IBOutlet weak var soccerField: WKInterfaceGroup! |
| @IBOutlet weak var verticalNudgeButton: WKInterfaceButton! |
| @IBOutlet weak var horizontalNudgeButton: WKInterfaceButton! |
Head back to Interface.storyboard and connect the interface controller to these outlets by Control-dragging from the interface controller to the elements. Once they’re all connected to the appropriate objects, open InterfaceController.swift and add a new type to represent the direction the ball is moving:
| enum ButtonDirection: String { |
| case Up = "↑" |
| case Down = "↓" |
| case Left = "←" |
| case Right = "→" |
| } |
The raw value of this enumeration type is String, you’ll use its values to populate buttons as they change. To do this, you’ll store the vertical and horizontal direction as properties, then update the buttons’ titles when they change:
| var verticalDirection: ButtonDirection = .Down { |
| didSet { |
| verticalNudgeButton.setTitle(verticalDirection.rawValue) |
| } |
| } |
| |
| var horizontalDirection: ButtonDirection = .Right { |
| didSet { |
| horizontalNudgeButton.setTitle(horizontalDirection.rawValue) |
| } |
| } |
In both cases, you use the didSet property observer to set the appropriate button’s title using the enum’s rawValue property.
Next, some other properties to help keep track of the game’s current state:
| let ballSize: CGSize = CGSize(width: 20, height: 20) |
| var currentInsets: UIEdgeInsets = UIEdgeInsetsZero |
| |
| var soccerFieldSize: CGSize { |
| var frame = contentFrame |
| frame.size.height -= 44 |
| return frame.size |
| } |
The ballSize constant allows you to compute the ball’s position, along with the currentInsets property. The insets start as UIEdgeInsetsZero, since the ball starts in the upper-left corner. Finally, you have the soccerFieldSize, which lets you know the size (in points) of the soccer field, so you don’t let the ball leave its field. To get that size, all you need to do is subtract the 44 points you left for the buttons from the interface controller’s contentFrame property.
In this method, you can use the contentFrame property of the interface controller to derive the soccer field size, which is equal but for the 44 points on the bottom where the buttons sit. With that in place, you just need to animate the ball when a user presses a button:
| let kickDistance: CGFloat = 10 |
| let kickDuration = 1.0 / 3.0 |
| |
| @IBAction func verticalNudgeButtonPressed() { |
| if verticalDirection == .Down { |
| currentInsets.top += kickDistance |
| |
| if currentInsets.top + ballSize.height + kickDistance > |
| soccerFieldSize.height { |
| verticalDirection = .Up |
| } |
| } |
| else { |
| currentInsets.top -= kickDistance |
| |
| if currentInsets.top - kickDistance < 0 { |
| verticalDirection = .Down |
| } |
| } |
| |
| animateWithDuration(kickDuration) { |
| self.soccerField.setContentInset(self.currentInsets) |
| } |
| } |
| |
| @IBAction func horizontalNudgeButtonPressed() { |
| if horizontalDirection == .Right { |
| currentInsets.left += kickDistance |
| |
| if currentInsets.left + ballSize.width + kickDistance > |
| soccerFieldSize.width { |
| horizontalDirection = .Left |
| } |
| } |
| else { |
| currentInsets.left -= kickDistance |
| |
| if currentInsets.left - kickDistance < 0 { |
| horizontalDirection = .Right |
| } |
| } |
| |
| animateWithDuration(kickDuration) { |
| self.soccerField.setContentInset(self.currentInsets) |
| } |
| } |
These methods are pretty similar. By adjusting the insets from the top and the left, you can move the ball around the screen. If the ball is too close to the edge of the screen, you flip that direction, which resets the button’s title and starts moving back the other way. Finally, in a call to animateWithDuration(duration:animations:), you animate this change.
Return to the storyboard and Control-drag from the buttons to the interface controller, connecting them to the appropriate outlets. Build and run the app and press the buttons. You’ll see the ball move around inside the field. Now, WatchKit doesn’t allow you to set the frame of an interface object directly, but, as you can see from this simple demo, the content inset of a group can get you pretty close to that functionality. Go ahead and play with the animation duration, the kick distance, or whatever you like.
Just like that, you made a simple animation-based game in about 100 lines of Swift using WKInterfaceGroup!
18.191.61.243