Skip to content

Instantly share code, notes, and snippets.

@amosgyamfi
Last active March 8, 2024 16:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amosgyamfi/b057ab41727621c99fefdc7cc89133c6 to your computer and use it in GitHub Desktop.
Save amosgyamfi/b057ab41727621c99fefdc7cc89133c6 to your computer and use it in GitHub Desktop.
//
// ContentView.swift
// PKDraw
import SwiftUI
import PencilKit
struct FreeFormDrawingView: View {
@State private var canvas = PKCanvasView()
@State private var isDrawing = true
@State private var color: Color = .black
@State private var pencilType: PKInkingTool.InkType = .pencil
@State private var colorPicker = false
@Environment(\.undoManager) private var undoManager
@State private var isMessaging = false
@State private var isVideoCalling = false
@State private var isScreenSharing = false
@State private var isRecording = false
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationStack {
DrawingView(canvas: $canvas, isDrawing: $isDrawing, pencilType: $pencilType, color: $color)
.navigationBarTitleDisplayMode(.inline)
// Top ornament: Start
.ornament(attachmentAnchor: .scene(.top)) {
HStack(spacing: 64) {
Button {
//
} label: {
VStack(spacing: 8) {
Image(systemName: "message")
Text("Chat")
.font(.caption2)
}
}
Button {
//
} label: {
VStack(spacing: 8) {
Image(systemName: "video")
Text("Call")
.font(.caption2)
}
}
// Screen sharing
Button {
//
} label: {
VStack(spacing: 8) {
Image(systemName: isScreenSharing ? "shared.with.you.slash" : "shared.with.you")
withAnimation {
Text(isScreenSharing ? "Stop" : "Share")
.font(.caption2)
}
}
}
// Screen recording
Button {
isRecording.toggle()
} label: {
//Image(systemName: "rectangle.dashed.badge.record")
VStack(spacing: 8) {
Image(systemName: isRecording ? "rectangle.inset.filled.badge.record" : "rectangle.dashed.badge.record")
withAnimation {
Text(isRecording ? "Stop" : "Record")
.font(.caption2)
}
}
}
}.padding(.horizontal)
.padding(12)
.glassBackgroundEffect()
.buttonStyle(.plain)
} // Top ornament: End
// Leading ornament: Start
.ornament(attachmentAnchor: .scene(.leading)) {
// Modify Tools
VStack(spacing: 32) {
Button {
// Clear the canvas. Reset the drawing
canvas.drawing = PKDrawing()
} label: {
Image(systemName: "scissors")
}
Button {
// Undo drawing
undoManager?.undo()
} label: {
Image(systemName: "arrow.uturn.backward")
}
Button {
// Redo drawing
undoManager?.redo()
} label: {
Image(systemName: "arrow.uturn.forward")
}
Button {
// Erase tool
isDrawing = false
} label: {
Image(systemName: "eraser.line.dashed")
}
.foregroundStyle(
LinearGradient(gradient: Gradient(colors: [.white, .yellow]), startPoint: .leading, endPoint: .top)
)
} // Modify tools
.padding(12)
.buttonStyle(.plain)
.glassBackgroundEffect(in: RoundedRectangle(cornerRadius: 32))
} // Leading ornament: End
.toolbar { // Bottom Ornament: Start
ToolbarItemGroup(placement: .bottomOrnament) {
HStack { // Drawing Tools
Button {
// Pencil
isDrawing = true
pencilType = .pencil
} label: {
VStack(spacing: 8) {
Image(systemName: "pencil.and.scribble")
Text("Pencil")
.foregroundStyle(.white)
}
}
.buttonStyle(.plain)
Button {
// Pen
isDrawing = true
pencilType = .pen
} label: {
VStack(spacing: 8) {
Image(systemName: "applepencil.tip")
Text("Pen")
.foregroundStyle(.white)
}
}
Button {
// Monoline
isDrawing = true
pencilType = .monoline
} label: {
VStack(spacing: 8) {
Image(systemName: "pencil.line")
Text("Monoline")
.foregroundStyle(.white)
}
}
Button {
// Fountain: Variable scribbling
isDrawing = true
pencilType = .fountainPen
} label: {
VStack(spacing: 8) {
Image(systemName: "scribble.variable")
Text("Fountain")
.foregroundStyle(.white)
}
}
Button {
// Marker
isDrawing = true
pencilType = .marker
} label: {
VStack(spacing: 8) {
Image(systemName: "paintbrush.pointed")
Text("Marker")
.foregroundStyle(.white)
}
}
Button {
// Crayon
isDrawing = true
pencilType = .crayon
} label: {
VStack(spacing: 8) {
Image(systemName: "paintbrush")
Text("Crayon")
.foregroundStyle(.white)
}
}
Button {
// Water Color
isDrawing = true
pencilType = .watercolor
} label: {
VStack(spacing: 8) {
Image(systemName: "eyedropper.halffull")
Text("Watercolor")
.foregroundStyle(.white)
}
}
// Color picker
Button {
// Pick a color
colorPicker.toggle()
} label: {
VStack(spacing: 8) {
Image(systemName: "paintpalette")
Text("Colorpicker")
.foregroundStyle(.white)
}
}
} // Drawing Tools
.padding(.horizontal)
.foregroundStyle(
LinearGradient(gradient: Gradient(colors: [.green, .yellow]), startPoint: .leading, endPoint: .bottom)
)
}
} // Bottom Ornament: End
// Trailing Ornament: Start
.ornament(attachmentAnchor: .scene(.trailing)) {
VStack(spacing: 32) {
Button {
// Set ruler as active
canvas.isRulerActive.toggle()
} label: {
Image(systemName: "pencil.and.ruler.fill")
}
Button {
// Tool picker
//let toolPicker = PKToolPicker.init()
@State var toolPicker = PKToolPicker()
toolPicker.setVisible(true, forFirstResponder: canvas)
toolPicker.addObserver(canvas)
canvas.becomeFirstResponder()
} label: {
Image(systemName: "pencil.tip.crop.circle.badge.plus")
}
// Menu for pencil types and color
Menu {
Button {
// Menu: Pick a color
colorPicker.toggle()
} label: {
Label("Color", systemImage: "paintpalette")
}
Button {
// Menu: Pencil
isDrawing = true
pencilType = .pencil
} label: {
Label("Pencil", systemImage: "pencil")
}
Button {
// Menu: pen
isDrawing = true
pencilType = .pen
} label: {
Label("Pen", systemImage: "pencil.tip")
}
Button {
// Menu: Marker
isDrawing = true
pencilType = .marker
} label: {
Label("Marker", systemImage: "paintbrush.pointed")
}
Button {
// Menu: Monoline
isDrawing = true
pencilType = .monoline
} label: {
Label("Monoline", systemImage: "pencil.line")
}
Button {
// Menu: pen
isDrawing = true
pencilType = .fountainPen
} label: {
Label("Fountain", systemImage: "paintbrush.pointed.fill")
}
Button {
// Menu: Watercolor
isDrawing = true
pencilType = .watercolor
} label: {
Label("Watercolor", systemImage: "eyedropper.halffull")
}
Button {
// Menu: Crayon
isDrawing = true
pencilType = .crayon
} label: {
Label("Crayon", systemImage: "pencil.tip")
}
} label: {
Image(systemName: "hand.draw")
}
.sheet(isPresented: $colorPicker) {
ColorPicker("Pick color", selection: $color)
.padding()
}
}.padding(12)
.buttonStyle(.plain)
.glassBackgroundEffect(in: RoundedRectangle(cornerRadius: 32))
} // Trailing Ornament: End
}
}
func saveDrawing() {
// Get the drawing image from the canvas
let drawingImage = canvas.drawing.image(from: canvas.drawing.bounds, scale: 1.0)
// Save drawings to the Photos Album
UIImageWriteToSavedPhotosAlbum(drawingImage, nil, nil, nil)
}
}
struct DrawingView: UIViewRepresentable {
// Capture drawings for saving in the photos library
@Binding var canvas: PKCanvasView
@Binding var isDrawing: Bool
// Ability to switch a pencil
@Binding var pencilType: PKInkingTool.InkType
// Ability to change a pencil color
@Binding var color: Color
//let ink = PKInkingTool(.pencil, color: .black)
// Update ink type
var ink: PKInkingTool {
PKInkingTool(pencilType, color: UIColor(color))
}
let eraser = PKEraserTool(.bitmap)
func makeUIView(context: Context) -> PKCanvasView {
// Allow finger and pencil drawing
canvas.drawingPolicy = .anyInput
canvas.tool = isDrawing ? ink : eraser
canvas.isRulerActive = true
canvas.backgroundColor = .init(red: 1, green: 1, blue: 0, alpha: 0.1)
// From Brian Advent: Show the default toolpicker
canvas.alwaysBounceVertical = true
canvas.isScrollEnabled = true
let toolPicker = PKToolPicker.init()
toolPicker.setVisible(true, forFirstResponder: canvas)
toolPicker.addObserver(canvas) // Notify when the picker configuration changes
canvas.becomeFirstResponder()
return canvas
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {
// Update tool whenever the main view updates
uiView.tool = isDrawing ? ink : eraser
}
}
#Preview {
FreeFormDrawingView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment