feat: add photo picker to select photo from library

This commit is contained in:
dscyrescotti
2024-06-15 12:37:41 +07:00
parent 4d637977e1
commit 49e878d0dd
5 changed files with 134 additions and 16 deletions

View File

@@ -83,6 +83,7 @@
ECA738F62BE612B700A4542E /* MTLDevice++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F52BE612B700A4542E /* MTLDevice++.swift */; };
ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FB2BE61C5200A4542E /* Persistence.swift */; };
ECA739082BE623F300A4542E /* PenDock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenDock.swift */; };
ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */; };
ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A852C19EE3900B96E12 /* ElementObject.swift */; };
ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A892C19EFB000B96E12 /* Element.swift */; };
ECD12A8C2C1AEAA900B96E12 /* PhotoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */; };
@@ -181,6 +182,7 @@
ECA738F52BE612B700A4542E /* MTLDevice++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTLDevice++.swift"; sourceTree = "<group>"; };
ECA738FB2BE61C5200A4542E /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
ECA739072BE623F300A4542E /* PenDock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDock.swift; sourceTree = "<group>"; };
ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPreview.swift; sourceTree = "<group>"; };
ECD12A852C19EE3900B96E12 /* ElementObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementObject.swift; sourceTree = "<group>"; };
ECD12A892C19EFB000B96E12 /* Element.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = "<group>"; };
ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoObject.swift; sourceTree = "<group>"; };
@@ -389,6 +391,7 @@
ECA7387B2BE5EF3500A4542E /* Memo */ = {
isa = PBXGroup;
children = (
ECBE52942C1D58F5006BDB3D /* PhotoPreview */,
EC1B783B2BFA0AAC005A34E2 /* Toolbar */,
EC5050082BF65D0500B4D86E /* Memo */,
EC5050052BF65CCD00B4D86E /* PenDock */,
@@ -665,6 +668,14 @@
path = Core;
sourceTree = "<group>";
};
ECBE52942C1D58F5006BDB3D /* PhotoPreview */ = {
isa = PBXGroup;
children = (
ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */,
);
path = PhotoPreview;
sourceTree = "<group>";
};
ECD12A872C19EF8700B96E12 /* Elements */ = {
isa = PBXGroup;
children = (
@@ -891,6 +902,7 @@
EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */,
ECA738A32BE6020A00A4542E /* CGFloat++.swift in Sources */,
ECA738C12BE60E5300A4542E /* PenStyle.swift in Sources */,
ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */,
ECA738DE2BE610A000A4542E /* ViewPortRenderPass.swift in Sources */,
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */,
EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */,

View File

@@ -14,8 +14,12 @@ public class Tool: NSObject, ObservableObject {
let object: ToolObject
@Published var pens: [Pen] = []
// MARK: - Pen
@Published var selectedPen: Pen?
@Published var draggedPen: Pen?
// MARK: - Photo
@Published var selectedImage: UIImage?
@Published var selection: ToolSelection = .none

View File

@@ -29,10 +29,18 @@ struct MemoView: View {
var body: some View {
CanvasView(tool: tool, canvas: canvas, history: history)
.ignoresSafeArea()
.overlay(alignment: .trailing) {
if tool.selection == .pen {
.overlay(alignment: .bottomTrailing) {
switch tool.selection {
case .pen:
PenDock(tool: tool, canvas: canvas)
.transition(.move(edge: .trailing))
case .photo:
if let image = tool.selectedImage {
PhotoPreview(image: image, tool: tool)
.transition(.move(edge: .trailing))
}
default:
EmptyView()
}
}
.overlay(alignment: .bottomLeading) {

View File

@@ -0,0 +1,48 @@
//
// PhotoPreview.swift
// Memola
//
// Created by Dscyre Scotti on 6/15/24.
//
import SwiftUI
struct PhotoPreview: View {
let image: UIImage
@ObservedObject var tool: Tool
var body: some View {
Image(uiImage: image)
.resizable()
.scaledToFill()
.frame(width: 100, height: 100)
.cornerRadius(5)
.overlay {
RoundedRectangle(cornerRadius: 5)
.stroke(Color.gray, lineWidth: 0.2)
}
.padding(10)
.background(.regularMaterial)
.cornerRadius(5)
.overlay(alignment: .topLeading) {
Button {
withAnimation {
tool.selectedImage = nil
}
} label: {
Image(systemName: "xmark.circle.fill")
.font(.title2)
.padding(1)
.contentShape(.circle)
.background {
Circle()
.fill(.white)
}
}
.foregroundStyle(.red)
.hoverEffect(.lift)
.offset(x: -12, y: -12)
}
.padding(10)
}
}

View File

@@ -6,6 +6,7 @@
//
import SwiftUI
import PhotosUI
import Foundation
struct Toolbar: View {
@@ -15,8 +16,10 @@ struct Toolbar: View {
@ObservedObject var canvas: Canvas
@ObservedObject var history: History
@State var memo: MemoObject
@State var title: String
@State var memo: MemoObject
@State var photoItem: PhotosPickerItem?
@FocusState var textFieldState: Bool
let size: CGFloat
@@ -50,6 +53,19 @@ struct Toolbar: View {
}
.font(.subheadline)
.padding(10)
.onChange(of: photoItem) { oldValue, newValue in
if newValue != nil {
Task {
let data = try? await newValue?.loadTransferable(type: Data.self)
if let data {
withAnimation {
tool.selectedImage = UIImage(data: data)
}
}
photoItem = nil
}
}
}
}
var closeButton: some View {
@@ -105,22 +121,52 @@ struct Toolbar: View {
.clipShape(.rect(cornerRadius: 8))
}
.hoverEffect(.lift)
Button {
withAnimation {
tool.selection = tool.selection == .photo ? .none : .photo
HStack(spacing: 0) {
Button {
withAnimation {
tool.selection = tool.selection == .photo ? .none : .photo
}
} label: {
Image(systemName: "photo")
.contentShape(.circle)
.frame(width: size, height: size)
.background(tool.selection == .photo ? Color.accentColor : Color.clear)
.foregroundStyle(tool.selection == .photo ? Color.white : Color.accentColor)
.clipShape(.rect(cornerRadius: 8))
}
.hoverEffect(.lift)
if tool.selection == .photo {
HStack(spacing: 0) {
Button {
} label: {
Image(systemName: "camera.fill")
.contentShape(.circle)
.frame(width: size, height: size)
.clipShape(.rect(cornerRadius: 8))
}
.hoverEffect(.lift)
PhotosPicker(selection: $photoItem, matching: .images) {
Image(systemName: "photo.fill.on.rectangle.fill")
.contentShape(.circle)
.frame(width: size, height: size)
.clipShape(.rect(cornerRadius: 8))
}
.hoverEffect(.lift)
}
}
}
.background {
if tool.selection == .photo {
RoundedRectangle(cornerRadius: 8)
.fill(Color.white.tertiary)
}
} label: {
Image(systemName: "photo")
.contentShape(.circle)
.frame(width: size, height: size)
.background(tool.selection == .photo ? Color.accentColor : Color.clear)
.foregroundStyle(tool.selection == .photo ? Color.white : Color.accentColor)
.clipShape(.rect(cornerRadius: 8))
}
.hoverEffect(.lift)
}
.background(.regularMaterial)
.clipShape(.rect(cornerRadius: 8))
.background {
RoundedRectangle(cornerRadius: 8)
.fill(.regularMaterial)
}
}
var historyControl: some View {