在 NativeScript 5.1 之前,您需要创建一个框架并将其包装在插件中才能使用自定义的 Objective-C/Swift 代码。由于这可能需要很多不必要的工作(尤其是在您想要添加一个小功能时),我们尝试使直接将 Objective-C 文件作为应用程序资源添加成为可能。
要将您的 Objective-C 文件添加到 NativeScript CLI Xcode 项目中,您需要执行三件事
1. 在 App_Resources/iOS 中创建 src 目录
CLI 将在此文件夹中查找,以便将它的内容添加到 Xcode 项目并编译和链接源文件。
2. 在 src 文件夹中添加源文件
3. 创建一个模块映射
module.modulemap
文件是必要的,这样元数据生成器才能找到声明并将它们添加到生成的 AST 中,从而使它们可以从您的 JavaScript 代码中访问。如果您不熟悉模块映射的概念,这里是一个很好的起点。
注意:当然,您可以将代码组织到单独的目录中,并且它们将作为
.xcodeproj
文件树中的单独目录添加。
对于这个演示,我选择了一个由 Daniel Larsson 撰写的关于 UIViewPropertyAnimator
的很棒教程。我使用原生代码教程的原因之一是为了作为示例,说明如何查找原生实现,然后在您的 NativeScript 应用中使用它们。
现在,如果我们要在 JavaScript 中实现它,我们可能需要将大块代码包装在 if (platform.ios)
块中,或者创建一个插件。我将要展示的另一种方法是在平台特定的(原生)代码中添加平台特定的功能。此外,在某些情况下,它在性能方面也可能更好 - 例如,当原生调用应该在动画视图时每帧执行一次,这样桥接器就不会每秒调用 60 次(在最好的情况下)。
顺便说一句,NativeScript 在这种情况下表现得非常好。
让我们从创建 NativeAnimator
类开始。因为我们正在编写 Objective-C 代码,所以现在我们需要两个文件。
不要忘记源文件需要位于 App_Resources/iOS/src
中。大多数情况下,您需要自己创建 src
目录。
NativeAnimator.h
#import <Foundation/Foundation.h>
@interface NativeAnimator : NSObject
-(id)initWithView:(UIView*)view andParent:(UIView*)parent;
-(void)setup;
@end
我们希望尽可能地隐藏实现细节,这就是为什么这些是 NativeAnimator
公开的唯一方法的原因。我更喜欢为附加 panGestureRecognizer
创建一个单独的方法,以便保持初始化程序只负责它应该负责的内容。
-(id)initWithView:(UIView*)view andParent:(UIView*)parent {
self = [super init];
if (self) {
self.playerView = view;
self.parentView = parent;
}
return self;
}
view
和 parent
参数是我们从 JavaScript 代码中传递的视图。NativeAnimator
需要保留对它们的引用才能完成其工作。现在我们准备创建 panGestureRecognizer
-(void)setup {
self.panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[self.parentView addGestureRecognizer:self.panGestureRecognizer];
}
以下是最终的实现代码
NativeAnimator.m
#import "NativeAnimator.h"
typedef NS_ENUM(NSInteger, PlayerState) {
PlayerStateThumbnail,
PlayerStateFullscreen,
};
@interface NativeAnimator ()
@property (weak, nonatomic) UIView *parentView;
@property (weak, nonatomic) UIView *playerView;
@property (nonatomic) UIViewPropertyAnimator *playerViewAnimator;
@property (nonatomic) CGRect originalPlayerViewFrame;
@property (nonatomic) PlayerState playerState;
@property (nonatomic) UIPanGestureRecognizer *panGestureRecognizer;
@property (nonatomic) UIView* b;
@end
@implementation NativeAnimator
-(id)initWithView:(UIView*)view andParent:(UIView*)parent {
self = [super init];
if (self) {
self.playerView = view;
self.parentView = parent;
}
return self;
}
-(void)setup {
self.panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[self.parentView addGestureRecognizer:self.panGestureRecognizer];
}
- (void)handlePan:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:self.parentView.superview];
if (recognizer.state == UIGestureRecognizerStateBegan)
{
[self panningBegan];
}
if (recognizer.state == UIGestureRecognizerStateEnded)
{
CGPoint velocity = [recognizer velocityInView:self.parentView];
[self panningEndedWithTranslation:translation velocity:velocity];
}
else
{
CGPoint translation = [recognizer translationInView:self.parentView.superview];
[self panningChangedWithTranslation:translation];
}
}
- (void)panningBegan
{
if (self.playerViewAnimator.isRunning)
{
return;
}
CGRect targetFrame;
switch (self.playerState) {
case PlayerStateThumbnail:
self.originalPlayerViewFrame = self.playerView.frame;
targetFrame = self.parentView.frame;
break;
case PlayerStateFullscreen:
targetFrame = self.originalPlayerViewFrame;
}
self.playerViewAnimator = [[UIViewPropertyAnimator alloc] initWithDuration:0.5 dampingRatio:0.8 animations:^{
self.playerView.frame = targetFrame;
}];
}
- (void)panningChangedWithTranslation:(CGPoint)translation
{
if (self.playerViewAnimator.isRunning)
{
return;
}
CGFloat translatedY = self.parentView.center.y + translation.y;
CGFloat progress;
switch (self.playerState) {
case PlayerStateThumbnail:
progress = 1 - (translatedY / self.parentView.center.y);
break;
case PlayerStateFullscreen:
progress = (translatedY / self.parentView.center.y) - 1;
}
progress = MAX(0.001, MIN(0.999, progress));
self.playerViewAnimator.fractionComplete = progress;
}
- (void)panningEndedWithTranslation:(CGPoint)translation velocity:(CGPoint)velocity
{
self.panGestureRecognizer.enabled = NO;
CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
__weak NativeAnimator *weakSelf = self;
switch (self.playerState) {
case PlayerStateThumbnail:
if (translation.y <= -screenHeight / 3 || velocity.y <= -100)
{
self.playerViewAnimator.reversed = NO;
[self.playerViewAnimator addCompletion:^(UIViewAnimatingPosition finalPosition) {
weakSelf.playerState = PlayerStateFullscreen;
weakSelf.panGestureRecognizer.enabled = YES;
}];
}
else
{
self.playerViewAnimator.reversed = YES;
[self.playerViewAnimator addCompletion:^(UIViewAnimatingPosition finalPosition) {
weakSelf.playerState = PlayerStateThumbnail;
weakSelf.panGestureRecognizer.enabled = YES;
}];
}
break;
case PlayerStateFullscreen:
if (translation.y >= screenHeight / 3 || velocity.y >= 100)
{
self.playerViewAnimator.reversed = NO;
[self.playerViewAnimator addCompletion:^(UIViewAnimatingPosition finalPosition) {
weakSelf.playerState = PlayerStateThumbnail;
weakSelf.panGestureRecognizer.enabled = YES;
}];
}
else
{
self.playerViewAnimator.reversed = YES;
[self.playerViewAnimator addCompletion:^(UIViewAnimatingPosition finalPosition) {
weakSelf.playerState = PlayerStateFullscreen;
weakSelf.panGestureRecognizer.enabled = YES;
}];
}
}
CGVector velocityVector = CGVectorMake(velocity.x / 100, velocity.y / 100);
UISpringTimingParameters *springParameters = [[UISpringTimingParameters alloc] initWithDampingRatio:0.8 initialVelocity:velocityVector];
[self.playerViewAnimator continueAnimationWithTimingParameters:springParameters durationFactor:1.0];
}
@end
唯一缺少的文件是 modulemap
。这是它
module.modulemap
module NativeAnimator {
header "NativeAnimator.h"
export *
}
如果您运行 tns prepare ios
,您将看到 NativeAnimator
文件现在是项目的一部分。干得好!
为了看到我们新创建的 NativeAnimator
实际工作,我们需要创建它将要使用的视图。
我假设您已经创建了一个普通的 js Hello World 应用!
一个很好的地方是在 onNavigatingTo
函数中执行此操作,因为我们可以在那里获取原生的 UIViewController
。
function onNavigatingTo(args) {
const page = args.object;
page.ios.playerView = UIView.alloc().initWithFrame(CGRectMake(100, 500, 100, 100));
page.ios.playerView.backgroundColor = UIColor.blackColor;
page.ios.view.addSubview(page.ios.playerView);
page.ios.animator = NativeAnimator.alloc().initWithViewAndParent(page.ios.playerView, page.ios.view);
page.ios.animator.setup();
...
}
这是结果
能够直接将 Objective-C 源代码添加到您的 NativeScript 应用中,消除了每次您需要创建 Objective-C 类并从 JavaScript 中访问它们时都要创建插件的麻烦。当然,创建插件 仍然是您想要抽象一些逻辑并使其可重用的最佳选择,因此请明智地选择。