npm package: @nativescript/swift-ui
Description: SwiftUI for NativeScript
Author: NativeScript
Latest version: 4.0.2

# @nativescript/swift-ui

Use SwiftUI with NativeScript.

## Contents
- [@nativescript/swift-ui](#nativescriptswift-ui)
  - [Contents](#contents)
  - [Installation](#installation)
  - [Usage](#usage)
    - [1. Create your SwiftUI view](#1-create-your-swiftui-view)
    - [2. Create your SwiftUI view Provider](#2-create-your-swiftui-view-provider)
    - [3. Register your SwiftUI with an identifier and use it in markup](#3-register-your-swiftui-with-an-identifier-and-use-it-in-markup)
      - [Core](#core)
      - [Generate Types](#generate-types)
      - [SwiftUI with Angular](#swiftui-with-angular)
      - [SwiftUI with Vue](#swiftui-with-vue)
      - [SwiftUI with React](#swiftui-with-react)
  - [Open Multiple Scenes](#open-multiple-scenes)
    - [Passing contextual data to scenes](#passing-contextual-data-to-scenes)
    - [Closing windows](#closing-windows)
  - [Use NativeScriptView inside SwiftUI](#use-nativescriptview-inside-swiftui)
  - [Credits](#credits)
  - [License](#license)

## Installation

```cli
npm install @nativescript/swift-ui
```

> **Note**: you will need to target iOS 13 at a minimum.
For example, you can add this line to your `App_Resources/iOS/build.xcconfig`:

```
IPHONEOS_DEPLOYMENT_TARGET = 13.0
```

> **Note**: If you would like to use `NativeScriptView` inside SwiftUI, you should target 14.0 minimum.

## Usage
![SwiftUI Source Files Example](/packages/swift-ui/swift-ui-source-files-example.png)
### 1. Create your SwiftUI view

This can be any SwiftUI view you'd like to create.

```swift
import SwiftUI

struct SampleView: View {

  var body: some View {
    VStack {
      Text("Hello World")
        .padding()
    }
  }
}
```

### 2. Create your SwiftUI view Provider 

This will prepare your SwiftUI for two-way data bindings to NativeScript and follows the plugins' `SwiftUIProvider` protocol to standardize all SwiftUI bindings.

```ts
import SwiftUI

@objc
class SampleViewProvider: UIViewController, SwiftUIProvider {

  // MARK: INIT

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }

  required public init() {
    super.init(nibName: nil, bundle: nil)
  }

  public override func viewDidLoad() {
    super.viewDidLoad()
    setupSwiftUIView(content: swiftUIView)
  }

  // MARK: PRIVATE

  private var swiftUIView = SampleView()

  /// Receive data from NativeScript
  func updateData(data: NSDictionary) {
      // can be empty
  }

  /// Allow sending of data to NativeScript
  var onEvent: ((NSDictionary) -> ())?
}
```

### 3. Register your SwiftUI with an identifier and use it in markup

This can be done in the bootstrap file (often `app.ts` or `main.ts`) or even the view component that needs it.

#### Core 

```typescript
import { 
  registerSwiftUI, 
  UIDataDriver
} from "@nativescript/swift-ui";

// A. You can generate types for your own Swift Provider with 'ns typings ios'
// B. Otherwise you can ignore by declaring the class name you know you provided
declare const SampleViewProvider: any;

registerSwiftUI("sampleView", (view) =>
  new UIDataDriver(SampleViewProvider.alloc().init(), view)
);

registerSwiftUI("barChart", (view) =>
  new UIDataDriver(BarChartProvider.alloc().init(), view)
);
```
Then insert it in any layout as follows:

```xml
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page" xmlns:sw="@nativescript/swift-ui">
  <StackLayout>
    <sw:SwiftUI swiftId="sampleView" height="150" />
  </StackLayout>
</Page>
```

#### Generate Types

1. To generate types for your SwiftUI code, run `ns typings ios`.

2. Locate the `objc!nsswiftsupport.d.ts` file in `typings/x86_64` and reference it in the `references.d.ts`.

#### SwiftUI with Angular

Register SwiftUI follows:

```ts
import { registerElement } from '@nativescript/angular'
import { SwiftUI } from '@nativescript/swift-ui'

registerElement('SwiftUI', () => SwiftUI)

registerSwiftUI("sampleView", (view) =>
  new UIDataDriver(SampleViewProvider.alloc().init(), view)
);
registerSwiftUI("barChart", (view) =>
  new UIDataDriver(BarChartProvider.alloc().init(), view)
);
```

It can now be used within any Angular component, eg:

```html
<StackLayout class="p-20">
    <SwiftUI swiftId="sampleView" height="150"></SwiftUI>
</StackLayout>
```
#### SwiftUI with Vue

Register SwiftUI follows:

```ts
registerElement("SwiftUIView", ()=> require("@nativescript/swift-ui").SwiftUI)

registerSwiftUI("sampleView", (view) =>
  new UIDataDriver(SampleViewProvider.alloc().init(), view)
);
registerSwiftUI("barChart", (view) =>
  new UIDataDriver(BarChartProvider.alloc().init(), view)
);
```
Then use it in markup as follows:

```xml
<StackLayout>

  <SwiftUIView swiftId="sampleView" height="200" />
  <SwiftUIView height="500" swiftId="barChart" margin="30" />

</StackLayout>
```
#### SwiftUI with React

Register SwiftUI follows:

```ts
registerSwiftUI("sampleView", (view) =>
  new UIDataDriver(SampleViewProvider.alloc().init(), view)
);

registerSwiftUI("barChart", (view) =>
  new UIDataDriver(BarChartProvider.alloc().init(), view)
);

interface SwiftUIViewAttributes extends ViewAttributes{
swiftId: string
}

declare global {
    module JSX {
        interface IntrinsicElements {

            swiftUIView: NativeScriptProps<SwiftUIViewAttributes, SwiftUI>
        }
    }
}

registerElement("swiftUIView", ()=> require("@nativescript/swift-ui").SwiftUI)
```
Then use it in markup as follows:

```xml
<stackLayout>
            
  <swiftUIView swiftId="sampleView" height="200" />
  <swiftUIView height="500" swiftId="barChart" margin="30" />

</stackLayout>
```

## Open Multiple Scenes

When using a SwiftUI App Lifecycle setup for your NativeScript app, *the default with* [visionOS](https://docs.nativescript.org/guide/visionos) *development*, you can enable multiple scenes in your `Info.plist` with the following:

```xml
<key>UIApplicationSceneManifest</key>
<dict>
  <key>UIApplicationPreferredDefaultSceneSessionRole</key>
  <string>UIWindowSceneSessionRoleApplication</string>
  <key>UIApplicationSupportsMultipleScenes</key>
  <true/>
  <key>UISceneConfigurations</key>
  <dict/>
</dict>
```

You can now use `WindowManager` (for use with standard Windows) or `XR` (for use with immersive spaces) to interact with multiple scenes, for example:

```swift
@main
struct NativeScriptApp: App {
  @State private var immersionStyle: ImmersionStyle = .mixed
  
  var body: some Scene {
    NativeScriptMainWindow()

    WindowGroup(id: "NeatView") {
        NeatView()
    }
    .windowStyle(.plain)

    ImmersiveSpace(id: "NeatImmersive") {
        NeatImmersive()
    }
    .immersionStyle(selection: $immersionStyle, in: .mixed, .full)
  }
}
```

You could open the `WindowGroup` with:

```ts
import { WindowManager } from "@nativescript/swift-ui";

WindowManager.getWindow("NeatView").open();
});
```

And you could open the `ImmersiveSpace` with:

```ts
import { XR } from "@nativescript/swift-ui";

XR.requestSession("NeatImmersive");
```

You could update either scene with:

```ts
import { WindowManager } from "@nativescript/swift-ui";

// Option A: inline
WindowManager.getWindow("NeatView").update({
  title: 'Updated Title'
});

// Option B: reference
const neatView = WindowManager.getWindow("NeatView");

neatView.update({
  title: 'Updated Title'
});

// Both options work with XR/Immersive Spaces as well, for example:
WindowManager.getWindow("NeatImmersive").update({
  salutation: 'Hello World'
});
```

### Passing contextual data to scenes

You can use the `onReceive` modifier in SwiftUI to handle any data passed to your windows.

For example, anytime `WindowManager.getWindow("Window_ID").update(...)` is called, a Notification is dispatched which can be picked up for data to be handled:

```swift
struct NeatView: View {
  @State var context: NativeScriptWindowContext?

  var body: some View {
    ZStack {
      // more neat views here
    }.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("NativeScriptWindowUpdate")), perform: { obj in
      context = NativeScriptWindowFactory.shared.getContextForId(id: "NeatView")

      let title = context!.data["title"] as! String 

      // use your updated title!           
    })
  }
}
```

### Closing windows

`WindowManager.getWindow("NeatView").close()` for a Window which is already open will close it.

`XR.endSession()` for an Immersive Space which is already open will close it.


## Use NativeScriptView inside SwiftUI

You can also use NativeScript view layouts and components inside SwiftUI when targeting iOS 14.0 minimum.

Add this line to your `App_Resources/iOS/build.xcconfig`:

```
IPHONEOS_DEPLOYMENT_TARGET = 14.0
```

You can now register as many NativeScript views by an id for usage:

```ts
import { SwiftUIManager } from '@nativescript/swift-ui';

SwiftUIManager.registerNativeScriptViews({
  Video: SceneVideoComponent
});
```

This will allow `SceneVideoComponent`, a NativeScript view component, to be used inside any SwiftUI component:

```swift
struct ContentView: View {

    var body: some View {

        ZStack {
            NativeScriptView(id: "Video")
        }
    }
}
```

## Credits

- WindowManager and XR APIs were established with the Callstack team. Shoutout to: [Oskar Kwaśniewski](https://github.com/okwasniewski).

<img src="https://raw.githubusercontent.com/valor-software/.github/d947b8547a9d5a6021e4f6af7b1df816c1c5f268/profile/valor-logo%20for-light.png#gh-light-mode-only" alt="Valor Software" width="200" />

NativeScript is proudly supported by Valor Software as an official partner. We are proud to offer guidance, consulting, and development assistance in all things NativeScript.

[Contact Valor for assistance](https://valor-software.com/).

## License

MIT