Finger and Pencil width on PKCanvasView Different - swiftui

I am trying to build out an application with the ability to use both apple pencil and finger input to draw. However, when I set the width of the tool to a sufficiently small number (1) the pencil input displays correctly, but when I use my finger it draws thicker lines. I would like uniformity in width when switching between pencil and finger. Any help would be appreciated. Bellow is the minimal reproducible code.
import Foundation
import SwiftUI
import PencilKit
struct ContentView: View {
var body: some View {
CanvasRepresentable()
}
}
struct CanvasRepresentable: UIViewRepresentable {
class Coordinator: NSObject, PKCanvasViewDelegate {
var parent: CanvasRepresentable
init(_ parent: CanvasRepresentable) {
self.parent = parent
}
func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeUIView(context: Context) -> PKCanvasView {
let c = PKCanvasView()
c.isOpaque = false
c.allowsFingerDrawing = true
c.delegate = context.coordinator
c.tool = PKInkingTool(.pen, color: .black, width: 1)
return c
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {
let tool = uiView.tool as! PKInkingTool
print(tool.width)
}
}

Related

UITapGestureRecognizer not triggered when used by 2 overlapping UIViewRepresentable

Because I need to know whether user taps with finger or pencil, I had to create a UIViewRepresentable with a UITapGestureRecognizer. Everything works fine when this View is used with other SwiftUI views. But when stack 2 of this view, and I clip the top one, the other one never catches any tap event.
Here is my custom view:
struct UIKitView: UIViewRepresentable {
let color: UIColor
func makeUIView(context: Context) -> UIView {
let view = UIView()
view.backgroundColor = color
let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap(gesture:)))
view.addGestureRecognizer(tapGesture)
return view
}
func updateUIView(_ uiView: UIView, context: Context) { }
func makeCoordinator() -> Coordinator {
Coordinator(color: color)
}
class Coordinator: NSObject {
let color: UIColor
init(color: UIColor) {
self.color = color
}
#objc func handleTap(gesture: UITapGestureRecognizer) {
print("Tapped in \(color == .yellow ? "yellow" : "red")")
}
}
}
And here how it is used in SwiftUI View:
ZStack {
UIKitView(color: .yellow)
UIKitView(color: .red)
.clipShape(Circle())) // not required, just visual
.contentShape(Circle()))
}
The tap is always catched by the red view.
Then I've overriden the hitTest() function of my UIVIewRepresentable to check if the tap is inside the clipîng shape. The tap is then detected in the red circle, but nothing tiggers anymore outside.
Any idea why?
You can override this method in CircleView:
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let center = CGPoint(x: bounds.size.width/2, y: bounds.size.height/2)
return pow(center.x-point.x, 2) + pow(center.y - point.y, 2) <= pow(bounds.size.width/2, 2)
}
Source
FYI there is also a mistake in your UIViewRepresentable, you need this
func updateUIView(_ uiView: UIView, context: Context) {
view.backgroundColor = color
}

pencil kit issue--drawing can't stay on canvas

I want to use pencil kit in my swiftui app. I encapsulate PKCanvasView in UIViewRepresentable. I can draw on canvas with only one stroke. However, the image will disappear immediately when I release the finger.
Am I missing anything?
import SwiftUI
import PencilKit
struct ContentView: View {
var body: some View {
CanvasView()
}
}
struct CanvasView: UIViewRepresentable {
func makeUIView(context: Context) -> PKCanvasView {
let canvas = PKCanvasView()
canvas.tool = PKInkingTool(.pen, color: .green, width: 10)
canvas.drawingPolicy = .anyInput
return canvas
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {
}
}
EDIT:
Xcode14.1, simulator iOS16.1

Remove title when pushing EKCalendarChooser to Navigation Stack with SwiftUI

I'm working on an app where I want to push the EKCalendarChooser View Controller to the navigation stack with a navigation link. Everything works as expected apart from the fact that I can't get rid of some magic title/label.
I want to hide the title marked with the red rectangle in the image.
I'm using the following code to push the view:
NavigationLink(destination: CalendarChooser(eventStore: self.eventStore)
.edgesIgnoringSafeArea([.top,.bottom])
.navigationTitle("My Navigation Title")) {
Text("Calendar Selection")
}
And this is my UIViewControllerRepresentable
import SwiftUI
import EventKitUI
struct CalendarChooser: UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
#Environment(\.presentationMode) var presentationMode
let eventStore: EKEventStore
func makeUIViewController(context: UIViewControllerRepresentableContext<CalendarChooser>) -> UINavigationController {
let chooser = EKCalendarChooser(selectionStyle: .multiple, displayStyle: .allCalendars, entityType: .event, eventStore: eventStore)
chooser.selectedCalendars = Set(eventStore.selectableCalendarsFromSettings)
chooser.delegate = context.coordinator
chooser.showsDoneButton = false
chooser.showsCancelButton = false
return UINavigationController(rootViewController: chooser)
}
func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext<CalendarChooser>) {
}
class Coordinator: NSObject, UINavigationControllerDelegate, EKCalendarChooserDelegate {
var parent: CalendarChooser
init(_ parent: CalendarChooser) {
self.parent = parent
}
func calendarChooserDidFinish(_ calendarChooser: EKCalendarChooser) {
let selectedCalendarIDs = calendarChooser.selectedCalendars.compactMap { $0.calendarIdentifier }
UserDefaults.savedCalendarIDs = selectedCalendarIDs
NotificationCenter.default.post(name: .calendarSelectionDidChange, object: nil)
parent.presentationMode.wrappedValue.dismiss()
}
func calendarChooserDidCancel(_ calendarChooser: EKCalendarChooser) {
parent.presentationMode.wrappedValue.dismiss()
}
}
}
Note that I'm not even sure that I'm on the right track here and I'm open for any solution.
I think I've found a solution to my own problem. With a small modification
to my UIViewControllerRepresentable the view looks the way I want it to. More specifically to the updateUIViewController function:
func updateUIViewController(_ uiViewController: UINavigationController, context: UIViewControllerRepresentableContext<CalendarChooser>) {
uiViewController.setNavigationBarHidden(true, animated: false) // This line!
}
By doing this I keep the navigation controls and title from the navigation link, which looks like this:

SwiftUI UIViewRepresentable ScrollView is not zooming

I am developing an iOS app using the SwiftUI. I need to implement the "pinch to zoom" feature in the app so i tried using the SwiftUI's ScrollView but went in to the problems of not able to pinch and drag the content at the same time as discussed in the question here. So i tried using the UIKit's UIScrollView in an UIViewRepresentable as suggested in the same thread. The problem i am facing now is when i pinch zoom the view the following error is displayed in the console and view doesn't zoom.
[Assert] -[UIScrollView _clampedZoomScale:allowRubberbanding:]: Must be called with non-zero scale
My UIViewRepresentable does not occupy the entire screen and is embedded in a VStack that occupies some portion on the screen along with other elements. I am using NavigationView that holds the VStack. I have an ObservableObject as a state on the main view. I update few #Published properties on this ObservableObject from .onAppear() of the main view. When i stop updating these properties, the zoom seems to be working as expected. I am not sure what is causing the issue, can some one please help if you have faced the above error? Thanks in advance.
I could replicate this with a sample code provided below.
TestScrollViewApp.swift
#main
struct TestScrollViewApp: App {
var body: some Scene {
WindowGroup {
TestView(interactor: TestInteractor())
}
}
}
TestView.swift
import Foundation
import SwiftUI
struct TestView: View {
#ObservedObject var interactor : TestInteractor
var body: some View {
ZStack{
NavigationView{
VStack{
Text("Hi there!")
HStack{
Text("HI i am beside map")
NativeScrollView()
}
Text("Hi I am footer!")
}
}.navigationViewStyle(StackNavigationViewStyle())
}.onAppear{
interactor.setUp()
}
}
}
struct NativeScrollView: UIViewRepresentable {
let imageview = UIImageView()
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UIScrollViewDelegate {
let parent: NativeScrollView
var zoomableView: UIView?
init(_ parent: NativeScrollView) {
self.parent = parent
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return zoomableView
}
}
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
scrollView.delegate = context.coordinator
scrollView.isScrollEnabled = true
scrollView.bouncesZoom = true;
imageview.contentMode = .scaleAspectFit
imageview.autoresizingMask = [.flexibleWidth,.flexibleHeight]
//add the image view
imageview.image = UIImage(named: "mapscreen")
scrollView.addSubview(imageview)
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 4.0
return scrollView
}
func updateUIView(_ uiView: UIScrollView, context: Context) {
context.coordinator.zoomableView = imageview
}
}
TestInteractor.swift
class TestInteractor:ObservableObject{
#Published var hasFooter = true
func setUp(){
hasFooter = false
}
}

SwiftUI: How Can I Present A View Over All Views? (Especially Sheets)

I want to show Image Viewer over all views when users tap into an image. It's working well without sheets but if there is a sheet on view, the image viewer stays behind it. How can I show image viewer over sheets too? I researched too much but I could not find any solution yet.
ContentView:
#ObservedObject var authVM: AuthVM = .shared
var body: some View {
ZStack{
TabView(selection: self.$authVM.selectedTab) {
HomeTab()
.tabItem {
Image(systemName: "house.fill")
.renderingMode(.template)
Text("Home")
}.tag(SelectedTab.home)
// Other tabs...
}
if self.authVM.showImageViewer{
PhotoViewer(viewerImages: $authVM.images, currentPageIndex: $authVM.imageIndex)
.edgesIgnoringSafeArea(.vertical)
}
}
}
I'm using SKPhotoBrowser pod (UIKit) with UIViewControllerRepresentable, maybe we can do something in UIKit to solve it?
import SwiftUI
import SKPhotoBrowser
struct PhotoViewer: UIViewControllerRepresentable {
#ObservedObject var authVM: AuthVM = .shared
#Binding var viewerImages:[SKPhoto]
#Binding var currentPageIndex: Int
func makeUIViewController(context: Context) -> SKPhotoBrowser {
SKPhotoBrowserOptions.displayHorizontalScrollIndicator = false
let browser = SKPhotoBrowser(photos: viewerImages)
browser.initializePageIndex(currentPageIndex)
browser.delegate = context.coordinator
return browser
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func updateUIViewController(_ browser: SKPhotoBrowser, context: Context) {
browser.photos = viewerImages
browser.currentPageIndex = currentPageIndex
}
class Coordinator: NSObject, SKPhotoBrowserDelegate {
var control: PhotoViewer
init(_ control: PhotoViewer) {
self.control = control
}
func didShowPhotoAtIndex(_ browser: PhotoViewer) {
self.control.currentPageIndex = browser.currentPageIndex
}
func didDismissAtPageIndex(_ index: Int) {
self.control.authVM.showImageViewer = false
}
}
}