I am building an Watch app where I want to overlay WKInterfaceImage with a group with a bunch of WKInterfaceLabel objects. Can't seem to be able to do this in StoryBoard editor.
Has anyone ever been able to achieve laying out views on top of each other for Watch App?
PS. I am aware of WKInterfaceGroup setBackgroundImage method. Since I want to do some animation inside WKInterfaceImage, setBackgroundImage is not going to woe for me
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 = (size.height/2)/kNumImagesInMapAnimations;
CGPoint fromPoint = CGPointMake(0, (step+1)*stepSize);
CGPoint toPoint = CGPointMake(0, -size.height/2 + (step+1)*stepSize);
[fromImage.fullImageWithoutArrows drawAtPoint:fromPoint];
[toImage.fullImageWithoutArrows drawAtPoint:toPoint];
}
else if (animation == GPWatchImageAnimationSlideBottomToTop)
{
CGFloat stepSize = (size.height/2)/kNumImagesInMapAnimations;
CGPoint fromPoint = CGPointMake(0, -(step+1)*stepSize);
CGPoint toPoint = CGPointMake(0, size.height/2 - (step+1)*stepSize);
[fromImage.fullImageWithoutArrows drawAtPoint:fromPoint];
[toImage.fullImageWithoutArrows drawAtPoint:toPoint];
}
else if (animation == GPWatchImageAnimationZoomOut)
{
CGFloat yStepSize = (size.height/2)/kNumImagesInMapAnimations;
CGFloat xStepSize = (size.width/2)/kNumImagesInMapAnimations;
//CGRect fromRect = CGRectMake((step + 1)*xStepSize, (step + 1)*yStepSize, size.width - 2*(step + 1)*xStepSize, size.height - 2*(step + 1)*yStepSize);
CGRect toRect = CGRectMake(-size.width/2 + (step+1)*xStepSize, -size.height/2 + (step+1)*yStepSize, size.width + 2*(kNumImagesInMapAnimations - step - 1)*xStepSize, size.height + 2*(kNumImagesInMapAnimations - step - 1)*yStepSize);
[toImage.fullImageWithoutArrows drawInRect:toRect];
//double alpha = (double)(kNumImagesInMapAnimations - step - 1)/(double)kNumImagesInMapAnimations;
//CGContextSetAlpha(UIGraphicsGetCurrentContext(), alpha);
//[fromImage.fullImageWithoutArrows drawInRect:fromRect];
//CGContextSetAlpha(UIGraphicsGetCurrentContext(), 1.0);
}
else if (animation == GPWatchImageAnimationZoomIn)
{
if (step == kNumImagesInMapAnimations -1)
{
[toImage.fullImageWithoutArrows drawAtPoint:CGPointMake(0, 0)];
}
else
{
CGFloat yStepSize = (size.height/2)/kNumImagesInMapAnimations;
CGFloat xStepSize = (size.width/2)/kNumImagesInMapAnimations;
CGRect fromRect = CGRectMake(-(step + 1)*xStepSize, -(step + 1)*yStepSize, size.width + 2*(step + 1)*xStepSize, size.height + 2*(step + 1)*yStepSize);
//CGRect toRect = CGRectMake(-size.width/2 + (step+1)*xStepSize, -size.height/2 + (step+1)*yStepSize, size.width + 2*(kNumImagesInMapAnimations - step - 1)*xStepSize, size.height + 2*(kNumImagesInMapAnimations - step - 1)*yStepSize);
[fromImage.fullImageWithoutArrows drawInRect:fromRect];
//[toImage.fullImageWithoutArrows drawInRect:fromRect];
}
}
else
{
[toImage.fullImageWithoutArrows drawAtPoint:CGPointMake(0, 0)];
}
UIImage* stepImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//Get each of the pieces of the image
GPWatchMapImage* mapImage = [[GPWatchMapImage alloc] init];
mapImage.showMapArrows = self.showMapArrows;
mapImage.fullImage = stepImg;
mapImage.watchSize = [self mapSize];
[topAnimatedImages addObject:mapImage.topImage];
[bottomAnimatedImages addObject:mapImage.bottomImage];
[leftAnimatedImages addObject:mapImage.leftImage];
[rightAnimatedImages addObject:mapImage.rightImage];
}
UIImage* topAnimatedImage = [UIImage animatedImageWithImages:topAnimatedImages duration:kMapAnimatinonDuration];
UIImage* bottomAnimatedImage = [UIImage animatedImageWithImages:bottomAnimatedImages duration:kMapAnimatinonDuration];
UIImage* leftAnimatedImage = [UIImage animatedImageWithImages:leftAnimatedImages duration:kMapAnimatinonDuration];
UIImage* rightAnimatedImage = [UIImage animatedImageWithImages:rightAnimatedImages duration:kMapAnimatinonDuration];
return @{@"top": topAnimatedImage, @"bottom": bottomAnimatedImage, @"left": leftAnimatedImage, @"right": rightAnimatedImage};
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With