Skip to content

Commit 30f66c2

Browse files
authored
Merge pull request #3 from liveview-native/0-4-0
Updates for client v0.4.0-rc.0
2 parents 7c3eaec + cd5e315 commit 30f66c2

5 files changed

Lines changed: 120 additions & 32 deletions

File tree

Package.resolved

Lines changed: 14 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ let package = Package(
1313
targets: ["LiveViewNativePhotoKit"]),
1414
],
1515
dependencies: [
16-
.package(url: "https://github.com/liveview-native/liveview-client-swiftui", branch: "core-file-upload")
16+
.package(url: "https://github.com/liveview-native/liveview-client-swiftui", from: "0.4.0-rc.0")
1717
],
1818
targets: [
1919
// Targets are the basic building blocks of a package, defining a module or a test suite.

README.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# liveview-native-swiftui-photokit
2+
3+
## About
4+
5+
`liveview-native-swiftui-photokit` is an add-on library for [LiveView Native](https://github.com/liveview-native/live_view_native). It adds [PhotoKit](https://developer.apple.com/documentation/photokit) support for uploading images from the Photos app and camera.
6+
7+
## Installation
8+
9+
1. Add this library as a package to your LiveView Native application's Xcode project
10+
* In Xcode, select *File**Add Packages...*
11+
* Enter the package URL `https://github.com/liveview-native/liveview-native-swiftui-photokit`
12+
* Select *Add Package*
13+
14+
## Usage
15+
16+
Add `.photoKit` to the `addons` list of your `#LiveView`.
17+
18+
```swift
19+
import SwiftUI
20+
import LiveViewNative
21+
import LiveViewNativePhotoKit // 1. Import the add-on library.
22+
23+
struct ContentView: View {
24+
var body: some View {
25+
#LiveView(
26+
.localhost,
27+
addons: [.photoKit] // 2. Include the `PhotoKit` addon.
28+
)
29+
}
30+
}
31+
```
32+
33+
Use a `PhotosPicker` or `LiveCameraPicker` to upload images.
34+
Include a `live_img_preview` to display the selected images before upload.
35+
36+
```elixir
37+
defmodule MyAppWeb.PhotosLive.SwiftUI do
38+
use MyAppNative, [:render_component, format: :swiftui]
39+
40+
def render(assigns) do
41+
~LVN"""
42+
<Form>
43+
<LiveForm id="upload-form" phx-submit="save" phx-change="validate">
44+
<PhotosPicker
45+
data-phx-upload-ref={@uploads.avatar.ref}
46+
maxSelectionCount="3"
47+
name="avatar"
48+
>
49+
<%= @uploads.avatar.ref %>
50+
<Label systemImage="photo.fill">Pick Photo</Label>
51+
</PhotosPicker>
52+
<.button type="submit">Upload</.button>
53+
</LiveForm>
54+
<%!-- use phx-drop-target with the upload ref to enable file drag and drop --%>
55+
<Section phx-drop-target={@uploads.avatar.ref}>
56+
<Text template="header" :if={not Enum.empty?(@uploads.avatar.entries)}>Uploads</Text>
57+
58+
<%!-- render each avatar entry --%>
59+
<%= for entry <- @uploads.avatar.entries do %>
60+
<VStack
61+
alignment="leading"
62+
style="swipeActions(content: :swipe_actions); listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8));"
63+
>
64+
<.live_img_preview entry={entry} style="resizable(); scaledToFit(); clipShape(.rect(cornerRadius: 4));" />
65+
66+
<%!-- entry.progress will update automatically for in-flight entries --%>
67+
<ProgressView value={entry.progress} total="100">
68+
<%= entry.client_name %>
69+
<Text template="currentValueLabel"><%= entry.progress%>%</Text>
70+
</ProgressView>
71+
72+
<%!-- Phoenix.Component.upload_errors/2 returns a list of error atoms --%>
73+
<%= for err <- upload_errors(@uploads.avatar, entry) do %>
74+
<Text style="foregroundStyle(.red); bold();"><%= error_to_string(err) %></Text>
75+
<% end %>
76+
77+
<%!-- a regular click event whose handler will invoke Phoenix.LiveView.cancel_upload/3 --%>
78+
<.button
79+
template="swipe_actions"
80+
role="destructive"
81+
phx-click="cancel-upload"
82+
phx-value-ref={entry.ref}
83+
>
84+
<Image systemName="trash" />
85+
</.button>
86+
</VStack>
87+
<% end %>
88+
89+
<%!-- Phoenix.Component.upload_errors/1 returns a list of error atoms --%>
90+
<%= for err <- upload_errors(@uploads.avatar) do %>
91+
<Text style="foregroundStyle(.red); bold();"><%= error_to_string(err) %></Text>
92+
<% end %>
93+
94+
</Section>
95+
</Form>
96+
"""
97+
end
98+
end
99+
```

Sources/LiveViewNativePhotoKit/LiveCameraPicker.swift

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ struct LiveCameraPicker<Root: RootRegistry>: View {
6060
guard let phxUploadRef else { return }
6161
Task {
6262
do {
63-
try await formModel?.queueFileUpload(id: phxUploadRef, contents: contents, fileType: fileType, name: name ?? "photo", coordinator: $liveElement.context.coordinator)
63+
try await formModel?.queueFileUpload(name: name ?? "photo", id: phxUploadRef, contents: contents, fileType: fileType, fileName: name ?? "photo", coordinator: $liveElement.context.coordinator)
6464
} catch {
6565
logger.log(level: .error, "\(error.localizedDescription)")
6666
}
@@ -199,8 +199,6 @@ struct LiveCameraPicker<Root: RootRegistry>: View {
199199
exporter.outputURL = exportURL
200200
await exporter.export()
201201

202-
print(exporter.error)
203-
204202
parent.onCapture(try Data(contentsOf: exportURL), .mpeg4Movie)
205203
parent.dismiss()
206204
} catch {
@@ -223,7 +221,7 @@ struct LiveCameraPicker<Root: RootRegistry>: View {
223221
}
224222
}
225223

226-
extension UIImagePickerController.CameraDevice: AttributeDecodable {
224+
extension UIImagePickerController.CameraDevice: @retroactive AttributeDecodable {
227225
public init(from attribute: Attribute?, on element: ElementNode) throws {
228226
guard let value = attribute?.value
229227
else { throw AttributeDecodingError.missingAttribute(Self.self) }
@@ -239,7 +237,7 @@ extension UIImagePickerController.CameraDevice: AttributeDecodable {
239237
}
240238
}
241239

242-
extension UIImagePickerController.CameraCaptureMode: AttributeDecodable {
240+
extension UIImagePickerController.CameraCaptureMode: @retroactive AttributeDecodable {
243241
public init(from attribute: Attribute?, on element: ElementNode) throws {
244242
guard let value = attribute?.value
245243
else { throw AttributeDecodingError.missingAttribute(Self.self) }
@@ -255,7 +253,7 @@ extension UIImagePickerController.CameraCaptureMode: AttributeDecodable {
255253
}
256254
}
257255

258-
extension UIImagePickerController.CameraFlashMode: AttributeDecodable {
256+
extension UIImagePickerController.CameraFlashMode: @retroactive AttributeDecodable {
259257
public init(from attribute: Attribute?, on element: ElementNode) throws {
260258
guard let value = attribute?.value
261259
else { throw AttributeDecodingError.missingAttribute(Self.self) }
@@ -273,7 +271,7 @@ extension UIImagePickerController.CameraFlashMode: AttributeDecodable {
273271
}
274272
}
275273

276-
extension UIImagePickerController.QualityType: AttributeDecodable {
274+
extension UIImagePickerController.QualityType: @retroactive AttributeDecodable {
277275
public init(from attribute: Attribute?, on element: ElementNode) throws {
278276
guard let value = attribute?.value
279277
else { throw AttributeDecodingError.missingAttribute(Self.self) }

Sources/LiveViewNativePhotoKit/PhotosPicker.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ struct PhotosPicker<Root: RootRegistry>: View {
5454
for photo in selection {
5555
guard let data = try await photo.loadTransferable(type: Data.self)
5656
else { continue }
57-
try await formModel?.queueFileUpload(id: phxUploadRef, contents: data, fileType: .png, name: name ?? "photo", coordinator: $liveElement.context.coordinator)
57+
try await formModel?.queueFileUpload(name: name ?? "photo", id: phxUploadRef, contents: data, fileType: .png, fileName: name ?? "photo", coordinator: $liveElement.context.coordinator)
5858
}
5959
} catch {
6060
logger.log(level: .error, "\(error.localizedDescription)")

0 commit comments

Comments
 (0)