「iOS 17」SwiftUIの新たな19本の機能とビュー（コード例付き）（WWDC 2023）

## 目次
- スクロールビューで特定の位置までスクロール
- App Storeで購入可能なアイテムを表示
- App Storeのサブスクリプションを表示
- 写真がNSFWかどうかを分析
- データストレージSwiftDataを使用
- SwiftUIのビュースタイルのためにMetalシェーダーを使用
- マップにマーカーやその他のコンポーネントを追加
- SFシンボルの画像エフェクト（パルス、反転、バウンス、スケール、表示/非表示、トランジション）
- 回転のジェスチャー
- インスペクタビュー(右側に表示されるサイドバー)
- 新しい#previewプレビューブロック
- foregroundStyleスタイルの使用
- TipKitを使ったヒントの表示
- Swift Macro

## はじめに

この記事の多くの部分（例えば、SwiftData、センシティブコンテンツ分析など）については、後ほどQiitaの記事でより詳しい解説を書く予定です。

:::note warn
※一般公開されている（一般に開示した情報）WWDC Keynoteの動画と公開Session/Documentationページだけを使ってこの記事を執筆しました。スクリーンショットはWWDCのセッション映像のものを使用しています。Xcodeのベータ版にアクセスできる場合は、自身でコードを実行されることをお勧めします。
:::

## ScrollView内で特定の位置までスクロールする

ScrollViewとその内部のスタック構造を使用している場合、
特定の行までビューをスクロールすることができます。

まず、.scrollTargetLayout()というビューモディファイアを、ScrollView内に主要な繰り返しコンテンツを含むレイアウトコンテナに追加します。これはVStack、LazyHStack、HStack、またはForEachなどになります。

```diff_swift
import SwiftUI

struct ScrollViewToRow: View {
    var body: some View {
        VStack {
            ScrollView {
                ForEach(1..<30, id: \.self) { number in
                    // ...
                }
+                .scrollTargetLayout()
            }
        }
    }
}
```
この例では、1から30までのすべての数字をループするためにForEachを使用しています。したがって、このビューモディファイアをForEachに追加します。

次に、scrollPositionビューモディファイアを使用してスクロール位置をバインドします。このビューモディファイアはScrollViewに添付されます。

```diff_swift
import SwiftUI

struct ScrollViewToRow: View {
+    @State private var scrollPosition: Int? = 0
    var body: some View {
        VStack {
            ScrollView {
                ForEach(1..<30, id: \.self) { number in
                    // ...
                }
                    .scrollTargetLayout()
            }
+            .scrollPosition(id: $scrollPosition)
        }
    }
}
```

オプションのInt型の@State変数を使用します。

新しい値を割り当てることでスクロール位置を更新することができます。コードをwithAnimationブロック内に配置すると、スクロールアニメーションが表示されます。

現在のスクロール位置を読み取ることもできます。

```swift
import SwiftUI

struct ScrollViewToRow: View {
    
    @State private var scrollPosition: Int? = 0
    
    var body: some View {
        
        VStack {
            
            Text("currently at \(scrollPosition ?? -1)")
            
            Button("Scroll") {
                withAnimation {
                    scrollPosition = 10
                }
            }
            
            ScrollView {
                ForEach(1..<30, id: \.self) { number in
                    HStack {
                        Text(verbatim: number.formatted())
                        Spacer()
                    }
                    .padding()
                    .foregroundStyle(.white)
                    .background {
                        RoundedRectangle(cornerRadius: 10)
                            .foregroundStyle(.teal)
                    }
                }
                    .scrollTargetLayout()
            }
            .scrollPosition(id: $scrollPosition)
            
        }
        .padding()
        
    }
    
}

#Preview {
    ScrollViewToRow()
}
```

## App Storeのアプリ内購入製品を表示

![IMG_1155.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/9059afe0-2633-6d8f-0504-97a8d702ea62.jpeg)

このビューでは、1つの製品を表示しています。
複数の商品を紹介するスクロールビューや水平スタックなどを作成することができます。

:::note
この話題については後で詳しくQiitaの記事を書きます
:::

App Storeから取得した製品IDを使用して、`ProductView`を初期化できます。

大きな製品ビュー（上記のBox of Nutrition Pelletsのような）を表示するためには、ビューモディファイア `.productViewStyle(.large)` を使用します。

```swift
ProductView(id: ids.nutritionPelletBox) {
    BoxOfNutritionPelletsIcon()
}
.productViewStyle(.large)
```

ProductViewのコードブロック内では、製品のアイコン（たとえば、Image）を提供できます。

## App Storeのすべてのサブスクリプションを表示

Appleから提供されるプレスタイルのビューを使用して、App Storeのサブスクリプションを表示することができます。

これを最も簡単に表示する方法は、SubscriptionStoreView(groupID: birdPassGroupID)を呼び出すだけで、非常にシンプルなビューが表示されます。

![IMG_1156.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/bf9950d9-5f77-4327-d85c-679ab7236264.jpeg)

:::note
この話題については後で詳しくQiitaの記事を書きます
:::

このビューはカスタマイズ可能で、カスタムヘッダー、背景、アイコンを表示できます。

まず、サブスクリプショングループIDを使用してSubscriptionStoreViewを初期化します。visibleRelationshipsでは、アップグレードのみを表示するか、すべてのオプション（ダウングレードオプションを含む）を表示するかを定義できます。

ブロック内では、PassMarketingContent（オプション）がヘッダーとして表示されます。ここでは、アプリのアイコン、サブスクリプショングループ名、サブスクリプションの利点を簡単に説明できます。

また、.subscriptionStoreControlIcon（オプション）ビューモディファイアを使用して、各サブスクリプションアイテムのアイコンを定義することもできます。

```swift
SubscriptionStoreView(
    groupID: passGroupID,
    visibleRelationships: showPremiumUpgrade ? .upgrade : .all
) {
    PassMarketingContent(showPremiumUpgrade: showPremiumUpgrade)
#if !os(watchOS)
        .containerBackground(for: .subscriptionStoreFullHeight) {
            SkyBackground()
        }
#endif
}
#if os(iOS)
.storeButton(.visible, for: .redeemCode)
#else
.frame(width: 400, height: 550)
#endif
.subscriptionStoreControlIcon { _, subscriptionInfo in
    Group {
        switch PassStatus(levelOfService: subscriptionInfo.groupLevel) {
            case .premium:
                Image(systemName: "bird")
            case .family:
                Image(systemName: "person.3.sequence")
            default:
                Image(systemName: "wallet.pass")
        }
    }
    .foregroundStyle(.accent)
    .symbolVariant(.fill)
}
#if !os(watchOS)
.backgroundStyle(.clear)
.subscriptionStoreButtonLabel(.multiline)
.subscriptionStorePickerItemBackground(.thinMaterial)
#endif
```

## SensitiveContentAnalysisを使用して画像を解析

https://developer.apple.com/documentation/sensitivecontentanalysis

:::note
この話題については後で詳しくQiitaの記事を書きます
:::

![rendered2x-1683741577.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/83f306b8-1a33-c2ba-9868-7692350711b7.png)

まず、センシティブコンテンツ（NSFW）アナライザーのエンタイトルメントをプロジェクトに追加する必要があります。

イメージアナライザーを初期化します：

```swift
let analyzer = SCSensitivityAnalyzer()
```

次に、アプリは、ユーザーがセンシティブコンテンツ分析機能をオンにしているかどうかをチェックします。

ユーザーは、システム設定（プライバシーセクション内）でこの機能をオンまたはオフにすることができます。この設定がオフの場合、画像の解析はできません。

```swift
switch analyzer.analysisPolicy {
    case .simpleInterventions:
        Label("Simple UI", systemImage: "checkmark")
        Text("Blur the image, and show a button to show the content.")
    case .descriptiveInterventions:
        Label("Detailed UI", systemImage: "checkmark")
        Text("Show a detailed description on how these types of images might affect the user.")
    case .disabled:
        Label("Not enabled", systemImage: "xmark")
        Text("To analyze a photo, turn on the sensitive photo warning first in system settings.")
}
```

その後、`CGImage`オブジェクトに変換することで、画像を解析することができます

```swift
func analyzePhoto(input: CGImage) {
    Task {
        let response = try await analyzer.analyzeImage(input)
        self.isImageSensitive = response.isSensitive
    }
}
```

以下は、フォトピッカーも含むSwiftUIのコードです。

```swift
import SwiftUI
import SensitiveContentAnalysis
import PhotosUI

struct ContentView: View {
    
    let analyzer = SCSensitivityAnalyzer()
    
    @State private var pickedPhotoToAnalyze: PhotosPickerItem?
    @State private var analyzedImage: UIImage?
    @State private var isImageSensitive: Bool?
    
    var body: some View {
        
        Form {
            
            Section("Status") {
                
                VStack(alignment: .leading) {
                    Text("Warning enabled in system settings")
                        .font(.headline)
                    switch analyzer.analysisPolicy {
                        case .simpleInterventions:
                            Label("Simple UI", systemImage: "checkmark")
                            Text("Blur the image, and show a button to show the content.")
                        case .descriptiveInterventions:
                            Label("Detailed UI", systemImage: "checkmark")
                            Text("Show a detailed description on how these types of images might affect the user.")
                        case .disabled:
                            Label("Not enabled", systemImage: "xmark")
                            Text("To analyze a photo, turn on the sensitive photo warning first in system settings.")
                    }
                }
                
            }
            .id(analyzer.analysisPolicy.rawValue)
            
            Section("Image analysis") {
                
                PhotosPicker("Pick a photo to analyze", selection: $pickedPhotoToAnalyze)
                    .onChange(of: pickedPhotoToAnalyze) { newValue in
                        if let newValue {
                            handlePickedImageItem(newValue)
                        }
                    }
                
                if let isImageSensitive {
                    if isImageSensitive {
                        Label("Oh no! What image did you pick?", systemImage: "eye.trianglebadge.exclamationmark")
                            .foregroundStyle(.red)
                    } else {
                        Label("It is OK!", systemImage: "checkmark")
                            .foregroundStyle(.green)
                    }
                }
                
                if let analyzedImage,
                   let isImageSensitive
                {
                    if isImageSensitive {
                        Image(uiImage: analyzedImage)
                            .resizable()
                            .scaledToFit()
                            .frame(height: 230)
                            .blur(radius: 5.0, opaque: true)
                    } else {
                        Image(uiImage: analyzedImage)
                            .resizable()
                            .scaledToFit()
                            .frame(height: 230)
                    }
                }
                
            }
            .disabled(analyzer.analysisPolicy == .disabled)
            
        }
        
    }
    
    func analyzePhoto(input: CGImage) {
        Task {
            let response = try await analyzer.analyzeImage(input)
            self.isImageSensitive = response.isSensitive
        }
    }
    
    func handlePickedImageItem(_ newValue: PhotosPickerItem) {
        self.isImageSensitive = nil
        self.analyzedImage = nil
        newValue.loadTransferable(type: Data.self) { result in
            switch result {
                case .success(let success):
                    if let success,
                       let imageObj = UIImage(data: success),
                       let cgImageObj = imageObj.cgImage
                    {
                        self.analyzedImage = imageObj
                        self.analyzePhoto(input: cgImageObj)
                    }
                case .failure(let failure):
                    return
            }
        }
    }
    
}

#Preview {
    ContentView()
}
```

## SwiftData

:::note
この話題については後で詳しくQiitaの記事を書きます
:::

SwiftDataはCore Dataを基盤に構築されています（ただし、Core Dataと組み合わせて使用する場合は、ハッシュをチェックするための追加の手順が必要です）。

以下の53行のコードで、保存されるデータの構造を定義し、コンテキストを作成し、新しいレコードを追加するためのSwiftUIビュー、レコードを表示し、削除するためのビューを作成しています。

```swift
import SwiftUI
import SwiftData

@Model
class Record {
    
    var id: UUID
    var timestamp: Date
    
    init(timestamp: Date) {
        self.id = UUID()
        self.timestamp = timestamp
    }
    
}

struct SwiftDataDemo: View {
    var body: some View {
        RecordsList()
            .modelContainer(for: [Record.self])
    }
}

struct RecordsList: View {
    @Environment(\.modelContext) private var modelContext
    
    @Query(sort: \.timestamp, order: .forward, animation: .snappy)
    var storedRecords: [Record]
    
    var body: some View {
        NavigationStack {
            List {
                ForEach(storedRecords) { record in
                    Text(record.timestamp, format: .dateTime)
                }
                .onDelete(perform: { indexSet in
                    indexSet.forEach({ modelContext.delete(storedRecords[$0]) })
                })
            }
            .toolbar {
                Button ("Add") {
                    let location = Record(timestamp: Date())
                    modelContext.insert(location)
                }
            }
        }
    }
}

#Preview {
    SwiftDataDemo()
}
```

上記のコードにおいて、以下のコードでデータ構造を定義しています：

```swift
@Model
class Record {
    
    var id: UUID
    var timestamp: Date
    
    init(timestamp: Date) {
        self.id = UUID()
        self.timestamp = timestamp
    }
    
}
```

ビューにストレージをアタッチします。まだ存在しない場合は、新しいストレージが作成されます。

```swift
struct SwiftDataDemo: View {
    var body: some View {
        RecordsList()
            .modelContainer(for: [Record.self])
    }
}
```

上記のビューモディファイアには、さらにinitパラメータがあり、例えば、データを自動的に保存するかどうか、完了時にコードを実行するかどうかなどを制御することができます。

また、iCloudを有効にし、iCloudコンテナを作成することも可能です。データはiCloudに同期されます

データのクエリには `@Query` を使用することができます

```swift
@Query(sort: \.timestamp, order: .forward, animation: .snappy)
var storedRecords: [Record]
```

レコードを削除するには、データベースを読む@Environmental変数を作成します。

```swift
@Environment(\.modelContext) private var modelContext
```

デフォルトでは、変更内容は自動的に保存されます。

## ShaderLibraryを使用して、ビュースタイルにMetalを適用

![Fx82SziaEAE-Fpy.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/4df9730a-9383-32d3-4845-fd03b87dbef6.jpeg)

まず、シェーダーの形状を定義する.metal定義ファイルを作成します。

```
//  angledFill.metal

#include <metal_stdlib>
using namespace metal;

[[ stitchable ]] half4
angledFill(float2 position, float width, float angle, half4 color)
{
    float pMagnitude = sqrt(position.x * position.x + position.y * position.y);
    float pAngle = angle +
    (position.x == 0.0f ? (M_PI_F / 2.0f) : atan(position.y / position.x));
    float rotatedX = pMagnitude * cos(pAngle);
    float rotatedY = pMagnitude * sin(pAngle);
    return (color + color * fmod(abs(rotatedX + rotatedY), width) / width) / 2;
}

```

次に、上記の.metal定義内の関数（angleFillと呼ばれる）を参照する新しいShaderシェーダーを作成します。

```swift
var stripes: Shader {
    ShaderLibrary.angledFill(
        .float(10),
        .float(90),
        .color(.blue)
    )
}
```

これで、`.foregroundStyle` ビューモディファイアを使って、テキストにShaderシェーダースタイルを適用できるようになりました。

```swift
Text("Furdinand").font(.system(size: 50).bold()).foregroundStyle(stripes) + Text(" is a good dog.").font(.system(size: 50).bold())
```

SwiftUIで異なるテキストコンポーネントを連結させることができます。

## .foregroundStyleについて、

https://developer.apple.com/documentation/swiftui/view/foregroundstyle(_:)

.foregroundStyleビューモディファイアを使用して、
SwiftUIビューに色またはMetalスタイルを設定することができます。

:::note
このビューモディファイアはiOS 15から使用可能ですが、iOS 17では入力パラメータとしてシェーダーを使用することができます。
:::

### >= iOS 15

iOS 15以降、.foregroundStyleを使用してビューの色やグラデーションを設定することができます。

```swift
Text("Furdinand")
    .font(.system(size: 50).bold())
    .foregroundStyle(.blue)
```

```swift
Text("Hello world")
    .foregroundStyle(.linearGradient(colors: [.teal, .blue], startPoint: .top, endPoint: .bottom))

```

### .foregroundColor`は非推奨 (will be deprecated) になるようです。

![スクリーンショット 2023-06-17 15.19.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/cdb5e5ca-02f9-7026-dbbf-247f2a544d25.png)

https://developer.apple.com/documentation/swiftui/view/foregroundcolor(_:)

:::note
もしiOS 14以前のデバイス向けのアプリをターゲットにしている場合、カスタムのビューモディファイアを作成することができます。一方、iOS 15以降のデバイス向けのアプリをターゲットにしている場合は、新しいforegroundStyleビューモディファイアにコードを置き換えることができます。

カスタムのビューモディファイア: https://gist.github.com/mszpro/fb6bb8a95376402daf433220de222389
:::

### >= iOS 17

iOS 17では入力パラメータとしてシェーダーを使用することができます。

```swift
var stripes: Shader {
    ShaderLibrary.angledFill(
        .float(10),
        .float(90),
        .color(.blue)
    )
}

Text("Furdinand")
    .font(.system(size: 50).bold())
    .foregroundStyle(stripes)
```


## SwiftUIでMapKitのマップを制御

以下は、WWDCのセッション動画からのスクリーンショットです。

Markerや図形（円、折れ線、多角形）を簡単に適用することができます。
また、@Binding変数を使用することで、現在どのMarkerが選択されているかを確認することができます。

![スクリーンショット 2023-06-16 12.01.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/556e4687-fe6b-9d67-5908-d9b65bc27ab0.png)

![スクリーンショット 2023-06-16 12.01.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/0f46bdac-7656-d275-4d88-fe1f9000a1f4.png)

![スクリーンショット 2023-06-16 12.01.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/9180fb24-5ce3-a290-76f2-c735e0b49f64.png)

:::note
この話題については後で詳しくQiitaの記事を書きます
:::


https://developer.apple.com/wwdc23/10043

## SFシンボルエフェクト

SwiftUIでSFシンボル画像に多くの視覚効果を加えることができる

### パルス

シンボルの不透明度をアニメーションで表示します。

```swift
Image(systemName: "rectangle.inset.filled.and.person.filled")
            .symbolEffect(.pulse)
            .frame(maxWidth: .infinity)
            .font(.system(size: 50))
            .symbolRenderingMode(.multicolor)
```

### リバーシング

シンボルをレイヤーごとにアニメーションさせることができます。例えば、WiFi接続のアニメーションを作成することができます。

```swift
Image(systemName: "wifi")
    .symbolEffect(.variableColor.iterative.reversing)
    .font(.system(size: 50))
    .symbolRenderingMode(.multicolor)
```

### バウンス（ハートビート効果）

画像のスケールをアニメーションで変化させます。画像を拡大し、縮小します。心臓の鼓動のようです。

```swift
Image(systemName: "arrow.down.circle")
    .symbolEffect(.bounce, value: simulatedDownloadPercentage)
    .font(.system(size: 50))
    .symbolRenderingMode(.multicolor)
```

### 拡大・縮小

上記と同様に、bool値を使ってシンボルを拡大・縮小することもできます。以下のサンプルコードでは、`simulatedDownloadPercentage` が偶数である場合に画像を拡大表示しています。

```swift
Image(systemName: "bubble.left.and.bubble.right.fill")
    .symbolEffect(.scale.up, isActive: simulatedDownloadPercentage % 2 == 0)
    .font(.system(size: 50))
    .symbolRenderingMode(.multicolor)
```

### 表示・非表示

```swift
Image(systemName: "cloud.sun.rain.fill")
    .symbolEffect(.disappear, isActive: simulatedDownloadPercentage % 2 == 0)
    .font(.system(size: 50))
    .symbolRenderingMode(.multicolor)
```

### 2つのシンボル間のトランジション効果

1つのシンボルから別のシンボルへの切り替え時に、トランジション効果を追加することができます。例えば、再生ボタンで、再生アイコンと一時停止アイコンを切り替えるような場合に使用できます。

```swift
VStack {
    Image(systemName: runToggle ? "play.fill" : "pause.fill")
        .contentTransition(.symbolEffect(.replace.downUp))
        .font(.system(size: 50))
        .symbolRenderingMode(.multicolor)
    Button("Toggle status") {
        runToggle.toggle()
    }
}
```

## 回転ジェスチャー

回転ジェスチャーを認識することで、ビューの回転を監視することができます。

```swift
struct RotationTesting: View {
    @State private var angleRotated = Angle(degrees: 0.0)

    var body: some View {
        Rectangle()
            .frame(width: 200, height: 200, alignment: .center)
            .rotationEffect(angleRotated)
            .gesture(
                RotateGesture()
                    .onChanged { value in
                        angleRotated = value.rotation
                    }
            )
    }
}
```

## インスペクタービュー（右側のサイドバー）を表示

インスペクターは右側に表示され、通常、ユーザーがアイテムの詳細を表示するために使用されます。

![inspector.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/e060a408-0610-e2a6-cf06-c17dc333a5c0.jpeg)

```swift
public struct ContentView: View {
    @State private var state = AppState()
    @State private var presented = true

    public var body: some View {
        AnimalTable(state: $state)
            .inspector(isPresented: $presented) {
                AnimalInspectorForm(animal: $state.binding())
                    .inspectorColumnWidth(
                        min: 200, ideal: 300, max: 400)
                    .toolbar {
                        Spacer()
                        Button {
                            presented.toggle()
                        } label: {
                            Label("Toggle Inspector", systemImage: "info.circle")
                        }
                    }
            }
    }
}
```

## 簡単にプレビュー

SwiftUIのビューをプレビューするために#previewを記述することができます。
また、#preview内で変数を直接追加することもできます。

```swift
#Preview {
    ContentView()
}
```

## TipKitを使ったユーザーへのチップ表示について

https://qiita.com/mashunzhe/items/016e991fb9020b3eb4c7

https://developer.apple.com/videos/play/wwdc2023/10229/

`TipKit`を使って、ユーザーにヒントを表示し、どこにどんな機能があるかを理解させることができます。

iOSベータ1では、ヒントを表示する際に問題があるかもしれませんが、
後でコードを更新します。

https://developer.apple.com/forums/thread/731073

![Fx53LuLakAENWgH.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/6f6c7596-f8d9-8ad8-1d25-63eb180f5105.jpeg)

![Fx9t6XFX0AElPzo.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/5916e6f2-94f6-160d-cccc-390b5f3189fa.jpeg)


## Macros

https://qiita.com/mashunzhe/items/40a5b1270c514606adcc

---

お読みいただき、ありがとうございました。

:relaxed: [Twitter @MszPro](https://twitter.com/MszPro)

:relaxed: 個人ウェブサイト https://MszPro.com

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/8b74bbc1-f44d-8d7d-1573-74a16232fe1f.png">


<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/33652157-7da9-ee68-3e4d-4b416edfec58.png">

---


## 注

上記内容の一部は、Apple社のサンプルコードから引用しています。ライセンスは下記に添付しています：

```
Copyright © 2023 Apple Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```