# 将 Objective-C 代码添加到 NativeScript 应用程序

·

在使用 NativeScript 5.1 之前,您必须创建一个框架并将其包装在插件中才能使用自定义的 Objective-C / Swift 代码。由于这可能是很多不必要的工作(特别是如果您想添加一个小功能),因此我们试图使将 Objective-C 文件直接添加为应用程序资源成为可能。

# 如何

为了将 Objective-C 文件添加到 NativeScript CLI Xcode 项目中,您需要做三件事:

  1. 在 App_Resources / iOS 中创建 src 目录

CLI 将查找此文件夹,以便将其内容添加到 Xcode 项目中,并编译和链接源文件。

  1. 将源文件添加到 src 文件夹中

  2. 创建模块图

module.modulemap文件是必需的,以便元数据生成器能够找到声明并将其添加到生成的 AST 中,以便可以从您的 JavaScript 代码访问它们。如果您不熟悉模块映射的概念,那么这里是一个不错的起点。

TIP

注意:当然,您可以将代码组织在单独的目录中,它们会像这样添加到.xcodeproj文件树中。

# 演示版

对于这个演示中,我选择了一个伟大的教程UIViewPropertyAnimator由丹尼尔·拉尔森。我使用本机代码教程的原因之一是作为示例,说明如何查找本机实现,然后在 NativeScript 应用程序中使用它们。

现在,如果要在 JavaScript 中实现此功能,则可能需要将大代码块包装在if (platform.ios)-block中或创建一个插件。我将向您展示的另一种方法是在特定于平台的(本机)代码中添加特定于平台的功能。此外,在某些情况下,它的性能也可能更好-例如,在为视图添加动画时应在每个帧上执行本机调用,因此,每秒不调用该桥 60 次(最佳情况下)。

即使在这种情况下,BTW NativeScript 的表现也非常好:
An image

用 javascript 拖动的示例

让我们从创建我们的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;
}

viewparent参数,我们会从我们的 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();
    ...
}

结果如下:
An image

使用本机代码拖动的示例

# 摘要

将 Objective-C 源代码直接添加到 NativeScript 应用程序的可能性消除了每次需要创建 Objective-C 类并从 JavaScript 访问它们时创建插件的麻烦。当然,当您要抽象一些逻辑并使其可重用时,创建插件仍然是最佳选择,因此请明智地选择。