我正在构建一个 Watch 应用程序,我想在其中将 WKInterfaceImage 与一个包含一堆 WKInterfaceLabel 对象的组重叠。在 StoryBoard 编辑器中似乎无法做到这一点。
有没有人能够为 Watch App 实现在彼此之上布局视图?
PS。我知道 WKInterfaceGroup setBackgroundImage 方法。因为我想在 WKInterfaceImage 中做一些动画,所以 setBackgroundImage 不会给我带来麻烦
您不能将 WKInterfaceObjects 布局在彼此之上。您可以在图像上渲染标签,然后设置它。您必须为动画的每一帧渲染标签。我在我的手表应用程序中为按钮执行此操作。我生成了一个占 UI 很大一部分的图像,然后生成动画的每一帧并覆盖每一帧的按钮文本。然后为每个帧剪切图像,以便我可以在每个按钮上制作动画。当您使用该应用程序时,它看起来像是按钮下的图像动画,而实际上它是 4 个不同按钮中的 4 个不同动画。
编辑
我想我会添加更多细节。这是我的应用程序的屏幕截图。我猜你想做类似的事情。
首先在我的故事板中,您需要确保组的间距为零。
为了创建这个 UI,我使用了这个帮助类。我已经编辑了这个类,只关注需要的部分。
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
接下来在我的 WKInterfaceController 类中,我这样做是为了制作动画。
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};
}
在 InterfaceBuilder 中:您可以只使用一个组并从您的资产中设置一个背景图像。然后,当您将 WKInterfaceImage 添加到该组中时,您有两个图层可以使用(如果两个图层对您来说足够了)。