You can't layout WKInterfaceObjects on top of each other. You can render the labels over your image and then set it. You will have to render the labels for each frame of the animation. I do this in my watch app for buttons. I generate an image that is a large piece of my UI, I then generate each frame of the animations and overlay the button text for each frame. Then a cut up the image for each frame so that I can the animations on each of the buttons. When you use the app it looks like the image is animating under the buttons, when in actuality it is 4 different animations in 4 different buttons.
Edit
I figured I would add some more detail. Here is a screen capture of my app. I am guessing that you want to do something similar.
First in my storyboard you need to make sure that the spacing for your groups in zero.
To create this UI I use this helper class. I have edited this class to focus on just the needed parts.
typedef struct
{
CGRect top;
CGRect left;
CGRect right;
CGRect bottom;
} ButtonRects;
@interface GPWatchMapImage ()
@property (readwrite, nonatomic) UIImage* topImage;
@property (readwrite, nonatomic) UIImage* bottomImage;
@property (readwrite, nonatomic) UIImage* leftImage;
@property (readwrite, nonatomic) UIImage* rightImage;
@property (readwrite, nonatomic) CGRect topRect;
@property (readwrite, nonatomic) CGRect bottomRect;
@property (readwrite, nonatomic) CGRect leftRect;
@property (readwrite, nonatomic) CGRect rightRect;
@property (readwrite, nonatomic) UIImage* fullImageWithoutArrows;
@property BOOL addedArrows;
@end
@implementation GPWatchMapImage
-(instancetype)init
{
self = [super init];
if (self)
{
}
return self;
}
-(UIImage*)leftImage
{
if (_leftImage == nil)
{
[self breakUpForButtons];
}
return _leftImage;
}
-(UIImage*)rightImage
{
if (_rightImage == nil)
{
[self breakUpForButtons];
}
return _rightImage;
}
-(UIImage*)topImage
{
if (_topImage == nil)
{
[self breakUpForButtons];
}
return _topImage;
}
-(UIImage*)bottomImage
{
if (_bottomImage == nil)
{
[self breakUpForButtons];
}
return _bottomImage;
}
-(UIImage*)fullImageWithoutArrows
{
[self fullImage]; //make sure we have the full image
if (_fullImageWithoutArrows != nil)
{
return _fullImageWithoutArrows;
}
return _fullImage;
}
-(UIImage*)fullImage
{
if (_fullImage == nil)
{
//This is the rect to create the image in
CGRect rect = CGRectMake(0, 0, self.watchSize.width, self.watchSize.height);
//This is how I generate map images. You will need to do something else
self.generatedMapInfo = [[GPCustomMapMaker instance] makeCustomMapFromConfig:self.mapConfig];
_fullImage = self.generatedMapInfo.mapImage;
}
if (self.showMapArrows && !self.addedArrows)
{
//Add the arrows
[self addButtonArrowsToFullImage];
}
return _fullImage;
}
-(void)addButtonArrowsToFullImage
{
self.addedArrows = YES;
ButtonRects rects = [self buttonRects];
UIImage* img = self.fullImage;
self.fullImageWithoutArrows = img; //save for animations
UIGraphicsBeginImageContext(img.size);
UIColor* color = [UIColor colorWithRed:.4 green:.4 blue:.4 alpha:.6];
//CGSize arrowSize = CGSizeMake(24, 4);
CGSize arrowSize = CGSizeMake(48, 8);
CGFloat edgeOffset = 26;
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 6);
CGContextSetLineJoin(ctx, kCGLineJoinRound);
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetStrokeColorWithColor(ctx, color.CGColor);
[img drawAtPoint:CGPointMake(0, 0)];
//Left arrow
CGPoint leftCenter = CGPointMake(rects.left.origin.x + edgeOffset, rects.left.origin.y + rects.left.size.height/2);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, leftCenter.x + arrowSize.height, leftCenter.y - arrowSize.width/2);
CGContextAddLineToPoint(ctx, leftCenter.x, leftCenter.y);
CGContextAddLineToPoint(ctx, leftCenter.x + arrowSize.height, leftCenter.y + arrowSize.width/2);
CGContextStrokePath(ctx);
CGPoint rightCenter = CGPointMake(rects.right.origin.x + rects.right.size.width - edgeOffset, rects.right.origin.y + rects.right.size.height/2);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, rightCenter.x, rightCenter.y - arrowSize.width/2);
CGContextAddLineToPoint(ctx, rightCenter.x + arrowSize.height, rightCenter.y);
CGContextAddLineToPoint(ctx, rightCenter.x, rightCenter.y + arrowSize.width/2);
CGContextStrokePath(ctx);
CGPoint topCenter = CGPointMake(rects.top.origin.x + rects.top.size.width/2, rects.top.origin.y + edgeOffset);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, topCenter.x - arrowSize.width/2, topCenter.y + arrowSize.height);
CGContextAddLineToPoint(ctx, topCenter.x, topCenter.y);
CGContextAddLineToPoint(ctx, topCenter.x + arrowSize.width/2, topCenter.y + arrowSize.height);
CGContextStrokePath(ctx);
CGPoint bottomCenter = CGPointMake(rects.bottom.origin.x + rects.bottom.size.width/2, rects.bottom.origin.y + rects.bottom.size.height - edgeOffset);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, bottomCenter.x - arrowSize.width/2, bottomCenter.y);
CGContextAddLineToPoint(ctx, bottomCenter.x, bottomCenter.y + arrowSize.height);
CGContextAddLineToPoint(ctx, bottomCenter.x + arrowSize.width/2, bottomCenter.y);
CGContextStrokePath(ctx);
UIImage* imgWithButtons = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
_fullImage = imgWithButtons;
}
-(UIImage*)subImageInRect:(CGRect)rect fromImage:(UIImage*)image
{
UIGraphicsBeginImageContext(rect.size);
[image drawInRect:CGRectMake(-rect.origin.x, -rect.origin.y, image.size.width, image.size.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
-(ButtonRects)buttonRects
{
UIImage* img = self.fullImage;
CGSize size = self.watchSize;
CGFloat topHeight = size.height * .25;
CGFloat bottomHeight = size.height * .25;
CGFloat middleHeight = size.height * .5;
CGRect topRect = CGRectMake(0, 0, size.width, topHeight);
CGRect leftRect = CGRectMake(0, topHeight, img.size.width/2.0, middleHeight);
CGRect rightRect = CGRectMake(img.size.width/2.0, topHeight, img.size.width/2.0, middleHeight);
CGRect bottomRect = CGRectMake(0, topHeight + middleHeight, size.width, bottomHeight);
ButtonRects rects;
rects.top = topRect;
rects.bottom = bottomRect;
rects.left = leftRect;
rects.right = rightRect;
return rects;
}
-(void)breakUpForButtons
{
UIImage* img = self.fullImage;
ButtonRects rects = [self buttonRects];
_topImage = [self subImageInRect:rects.top fromImage:img];
_leftImage = [self subImageInRect:rects.left fromImage:img];
_rightImage = [self subImageInRect:rects.right fromImage:img];
_bottomImage = [self subImageInRect:rects.bottom fromImage:img];
}
@end
Next in my WKInterfaceController class I do this to make the animations.
typedef NS_ENUM(NSInteger, GPWatchImageAnimation)
{
GPWatchImageAnimationNone,
GPWatchImageAnimationSlideLeftToRight,
GPWatchImageAnimationSlideRighToLeft,
GPWatchImageAnimationSlideTopToBottom,
GPWatchImageAnimationSlideBottomToTop,
GPWatchImageAnimationZoomIn,
GPWatchImageAnimationZoomOut
};
#define kNumImagesInMapAnimations 6
#define kMapAnimatinonDuration 0.4
...
-(void)updateMapImageWithAnimation:(GPWatchImageAnimation)animation
{
GPWatchMapImage* previousImage = self.currentMapImage;
//This gets the size of the full image that you want
CGSize size = [self mapSize];
self.currentMapImage = [[GPWatchMapImage alloc] init];
self.currentMapImage.watchSize = size;
//Check if we have a previous image to animate from
if (previousImage != nil && animation != GPWatchImageAnimationNone)
{
NSDictionary* animatedImage = [self animateFromImage:previousImage toImage:self.currentMapImage withAnimation:animation];
[self.mapTopImage setImage:[animatedImage objectForKey:@"top"]];
[self.mapLeftImage setImage:[animatedImage objectForKey:@"left"]];
[self.mapRightImage setImage:[animatedImage objectForKey:@"right"]];
[self.mapBottomImage setImage:[animatedImage objectForKey:@"bottom"]];
[self.mapTopImage startAnimatingWithImagesInRange:NSMakeRange(0, kNumImagesInMapAnimations) duration:kMapAnimatinonDuration repeatCount:1];
[self.mapLeftImage startAnimatingWithImagesInRange:NSMakeRange(0, kNumImagesInMapAnimations) duration:kMapAnimatinonDuration repeatCount:1];
[self.mapRightImage startAnimatingWithImagesInRange:NSMakeRange(0, kNumImagesInMapAnimations) duration:kMapAnimatinonDuration repeatCount:1];
[self.mapBottomImage startAnimatingWithImagesInRange:NSMakeRange(0, kNumImagesInMapAnimations) duration:kMapAnimatinonDuration repeatCount:1];
}
else
{
[self.mapTopImage setImage:self.currentMapImage.topImage];
[self.mapLeftImage setImage:self.currentMapImage.leftImage];
[self.mapRightImage setImage:self.currentMapImage.rightImage];
[self.mapBottomImage setImage:self.currentMapImage.bottomImage];
}
}
-(NSDictionary*)animateFromImage:(GPWatchMapImage*)fromImage toImage:(GPWatchMapImage*)toImage withAnimation:(GPWatchImageAnimation)animation
{
NSMutableArray* topAnimatedImages = [[NSMutableArray alloc] initWithCapacity:kNumImagesInMapAnimations];
NSMutableArray* bottomAnimatedImages = [[NSMutableArray alloc] initWithCapacity:kNumImagesInMapAnimations];
NSMutableArray* leftAnimatedImages = [[NSMutableArray alloc] initWithCapacity:kNumImagesInMapAnimations];
NSMutableArray* rightAnimatedImages = [[NSMutableArray alloc] initWithCapacity:kNumImagesInMapAnimations];
CGSize size = fromImage.fullImage.size;
for (int step = 0; step < kNumImagesInMapAnimations; step++)
{
UIGraphicsBeginImageContext(size);
//render this step
if (animation == GPWatchImageAnimationSlideLeftToRight)
{
CGFloat stepSize = (size.width/2)/kNumImagesInMapAnimations;
CGPoint fromPoint = CGPointMake(-(step+1)*stepSize, 0);
CGPoint toPoint = CGPointMake(size.width/2 - (step+1)*stepSize, 0);
[fromImage.fullImageWithoutArrows drawAtPoint:fromPoint];
[toImage.fullImageWithoutArrows drawAtPoint:toPoint];
}
else if (animation == GPWatchImageAnimationSlideRighToLeft)
{
CGFloat stepSize = (size.width/2)/kNumImagesInMapAnimations;
CGPoint fromPoint = CGPointMake((step+1)*stepSize, 0);
CGPoint toPoint = CGPointMake(-size.width/2 + (step+1)*stepSize, 0);
[fromImage.fullImageWithoutArrows drawAtPoint:fromPoint];
[toImage.fullImageWithoutArrows drawAtPoint:toPoint];
}
else if (animation == GPWatchImageAnimationSlideTopToBottom)
{
CGFloat stepSize = (s