Override right click in SwiftUI - cocoa

I try to add additional functionality when doing a right click. Unfortunately, this will break the .context modifier and I don't understand why. This is the code I am using:
extension View {
func onRightClick(handler: #escaping () -> Void) -> some View {
modifier(RightClickHandler(handler: handler))
}
}
struct RightClickHandler: ViewModifier {
let handler: () -> Void
func body(content: Content) -> some View {
content.overlay(RightClickListeningViewRepresentable(handler: handler), alignment: .center)
}
}
struct RightClickListeningViewRepresentable: NSViewRepresentable {
let handler: () -> Void
func makeNSView(context: Context) -> RightClickListeningView {
RightClickListeningView(handler: handler)
}
func updateNSView(_ nsView: RightClickListeningView, context: Context) {}
}
class RightClickListeningView: NSView {
let handler: () -> Void
init(handler: #escaping () -> Void) {
self.handler = handler
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func rightMouseDown(with event: NSEvent) {
handler()
super.rightMouseDown(with: event)
if let menu = super.menu(for: event) {
print(menu)
let location = event.locationInWindow
menu.popUp(positioning: nil, at: location, in: self)
}
}
}
Later then, I have this:
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.contextMenu(menuItems: {
Button("Test") { }
})
.onRightClick {
print("right click detcted")
}
.padding()
}
}
If I remove the onRightClick-modifier, the context menu works again.

I know it's 6 months since you've asked this but if you swap around the modifiers it will work as intended (assuming you are trying to detect when context menu is opened). When you right click, the context menu will open and the right click will be detected. Hope this helps!
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.onRightClick {
print("right click detcted")
}
.contextMenu(menuItems: {
Button("Test") { }
})
.padding()
}
}

Related

NSContactPicker not displaying picker window in SwiftUI on macOS

I have tried to get the NSContactPicker to display a picker window in SwiftUI on macOS. Here is my code. If you click on the button nothing happens. What am I missing?
import SwiftUI
import Contacts
import ContactsUI
let d = MyContactPicker()
class MyContactPicker: NSObject, CNContactPickerDelegate
{
var contactName: String = "No user selected"
func pickContact()
{
let contactPicker = CNContactPicker()
contactPicker.delegate = self
}
func contactPicker(_ picker: CNContactPicker, didSelect contact: CNContact)
{
contactName = contact.givenName
}
}
struct ContentView: View
{
#State var contact: CNContact?
var picker = MyContactPicker()
var body: some View
{
VStack
{
Text(picker.contactName)
Button("Select Contact")
{
picker.pickContact()
}
}
}
}
Here's a possible starting point using NSViewRepresentable and an NSView subclass
class NSContactPickerView: NSView, CNContactPickerDelegate {
let didSelectContact: (CNContact) -> Void
init(didSelectContact: #escaping (CNContact) -> Void) {
self.didSelectContact = didSelectContact
super.init(frame: .zero)
Task {
showPicker()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func showPicker() {
let picker = CNContactPicker()
picker.delegate = self
picker.showRelative(to: .zero, of: self, preferredEdge: .maxY)
}
func contactPicker(_ picker: CNContactPicker, didSelect contact: CNContact) {
didSelectContact(contact)
}
}
struct ContactPicker: NSViewRepresentable {
let didSelectContact: (CNContact) -> Void
func makeNSView(context: Context) -> NSContactPickerView {
NSContactPickerView(didSelectContact: didSelectContact)
}
func updateNSView(_ nsView: NSContactPickerView, context: Context) {
}
}
struct ContentView: View {
#State var contact: CNContact?
#State private var showPicker = false
var body: some View {
VStack {
Text(contact?.givenName ?? "")
Button("Select Contact") {
showPicker = true
}
}
.sheet(isPresented: $showPicker) {
ContactPicker { contact in
self.contact = contact
}
.frame(width: 1, height: 1)
}
}
}
It works, but it's not very elegant. Maybe someone else can improve on this.

Back Button not working quickly, Take 4-5 Second Webview to back, How to get Progress SwiftUI

I'm writing a Webapp in swiftUI with xcode13.1 I've a question "How can I add a progress bar that shows me the loading of page when user click on back button?
import SwiftUI
import WebKit
struct WebViewForCommunityTab : UIViewRepresentable {
let request: URLRequest
var webview: WKWebView?
init(web: WKWebView?, req: URLRequest) {
self.webview = WKWebView()
self.request = req
}
func makeUIView(context: Context) -> WKWebView {
return webview!
}
func updateUIView(_ uiView: WKWebView, context: Context) {
uiView.load(request)
}
func goBack(){
webview?.goBack()
}
func goForward(){
webview?.goForward()
}
func goHomeScreen(){
webview?.load(URLRequest(url: (URL(string: DefaultUrlString.CommunityWebLink)!)))
}
}
struct WebContentView: View {
let webview = WebViewForHomeTab(web: nil, req: URLRequest(url: URL(string: DefaultUrlString)!))
var body: some View {
ZStack {
VStack {
VStack (spacing: 80) {
webview
}
HStack (alignment: .center) {
Group {
Spacer()
CustomButtonForWebView(btnImageTitle: ArrowLeftCircleImg, action: {
self.webview.goBack()
})
CustomButtonForWebView(btnImageTitle: ArrowRightCircleImg, action: {
self.webview.goForward()
})
Spacer()
}.padding(.bottom,10)
}.frame(width: UIScreen.screenWidth, height: 40, alignment: .center)
}.hiddenNavigationBarStyle().background(Color.white)
}
}
}
Question: How to get Progress in the WKWebView when user click on back button?
Can someone please explain to me How to get Progress?
Any help would be greatly appreciated.
Thanks in advance.

NSButton inside a NSViewRepresentable gives no "visual feedback" if clicked

I built a NativeButton based on a NSButton with a NSViewRepresentable to detect Rightclicks.
It works fine, but I do not get "visual feedback" as with a normal Button, if it's clicked.
How can I accomplish that the Button get dark, if it's clicked (as any normal Button)?
struct TEST_NativeButton: View {
var body: some View {
VStack{
Text("Test Native Button")
NativeButton(
rightClickAction: {print("rightclick")},
leftClickAction: {print("leftclick")}
)
{ print("standard action")
}
}.frame(width: 200, height: 200)
}
}
struct NativeButton: NSViewRepresentable {
typealias NSViewType = NativeNSButton
let rightClickAction: ActionFunction
let leftClickAction: ActionFunction
let standardAction: ActionFunction
init(
rightClickAction : ActionFunction? = nil,
leftClickAction : ActionFunction? = nil,
standardAction: #escaping ActionFunction
)
{
if let rightClickAction = rightClickAction{
self.rightClickAction = rightClickAction
} else {
self.rightClickAction = {}
}
if let leftClickAction = leftClickAction{
self.leftClickAction = leftClickAction
} else {
self.leftClickAction = standardAction
}
self.standardAction = standardAction
}
func makeNSView(context: Context) -> NativeNSButton {
NativeNSButton(
rightClickAction: rightClickAction,
leftClickAction: leftClickAction,
standardAction: standardAction)
}
func updateNSView(_ nsView: NativeNSButton, context: Context) {
//ToDo auf änderungen am Titel reagieren
return
}
}
class NativeNSButton: NSButton {
let standardAction: () -> Void
let rightClickAction: () -> Void
let leftClickAction: () -> Void
init(
rightClickAction : #escaping () -> Void,
leftClickAction : #escaping () -> Void,
standardAction: #escaping () -> Void) {
self.standardAction = standardAction
self.rightClickAction = rightClickAction
self.leftClickAction = leftClickAction
super.init(frame: .zero)
self.title = title
self.alignment = alignment
self.target = self
self.action = #selector(clickButton(_:))
bezelStyle = .rounded
isBordered = true
focusRingType = .none
self.translatesAutoresizingMaskIntoConstraints = false
self.setContentHuggingPriority(.defaultHigh, for: .vertical)
self.setContentHuggingPriority(.defaultHigh, for: .horizontal)
}
required init?(coder: NSCoder) {
fatalError()
}
override func mouseDown(with theEvent: NSEvent) {
//print("left mouse")
leftClickAction()
}
override func rightMouseDown(with theEvent: NSEvent) {
//print("right mouse")
rightClickAction()
}
#objc func clickButton(_ sender: BbcNSButton) {
//print("standard action")
standardAction()
}
}
The standard action is used for detection Keyboard-Shortcuts (removed in this example).
Call super method
override func mouseDown(with theEvent: NSEvent) {
super.mouseDown(with: theEvent)
//print("left mouse")
leftClickAction()
}

How to invoke a method on a NSViewRepresentable from a View?

I'm having a hard time to understand how NSViewRepresentable interacts with a SwiftUI view.
For example, in the example below, how can I load a different URL when the button in the view is clicked?
import Cocoa
import SwiftUI
import WebKit
import PlaygroundSupport
struct InternalBrowser: NSViewRepresentable {
func makeNSView(context: Context) -> WKWebView {
let browser = WKWebView()
browser.load(URLRequest(url: URL(string: "https://www.google.com")!))
return browser
}
func updateNSView(_ nsView: WKWebView, context: Context) {
}
}
struct Browser: View {
var body: some View {
GeometryReader { g in
VStack {
InternalBrowser().frame(width: 400, height: 400)
Button(action: {
// how to tell InternalBrowser to load an URL here?
}) {
Text("Load Different URL")
}
}
}
}
}
PlaygroundPage.current.setLiveView(Browser().frame(width: 500, height: 500))
how can I load a different URL when the button in the view is clicked?
Here is a demo of possible approach. Tested with Xcode 11.4 / iOS 13.4
struct InternalBrowser: NSViewRepresentable {
#Binding var url: URL?
func makeNSView(context: Context) -> WKWebView {
return WKWebView()
}
func updateNSView(_ browser: WKWebView, context: Context) {
if let url = self.url, url != browser.url {
browser.load(URLRequest(url: url))
}
}
}
struct Browser: View {
#State var resourceURL = URL(string: "https://www.google.com")
var body: some View {
VStack {
Button(action: {
self.resourceURL = URL(string: "https://www.stackoverflow.com")
}) {
Text("Load Different URL")
}
Divider()
InternalBrowser(url: self.$resourceURL).frame(width: 400, height: 400)
}
}
}

UIView[Controller]Representable: SwiftUI Subview is shown in Debugger, but not when run

Because the ScrollView does not provide a function to set the contentOffset, I'm trying to use the UIScrollView as UIViewRepresentable. The attached code shows both, the caller and the definition of the view and the view controller.
When running the code in simulator or previews, just a blue area is shown. When debugging the display, the Text is shown, as expected.
If have no idea about the reason - is it because I'm doing something wrong, or because there's a bug in Xcode or SwiftUI?
Here the custom scroll view:
struct PositionableScrollView<Content>: UIViewRepresentable where Content: View {
var content: () -> Content
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content
}
func makeUIView(context: UIViewRepresentableContext<PositionableScrollView<Content>>) -> UIScrollView {
let scrollViewVC = PositionableScrollViewVC<Content>(nibName: nil, bundle: nil)
scrollViewVC.add(content: content)
let control = scrollViewVC.scrollView
return control
}
func updateUIView(_ uiView: UIScrollView, context: UIViewRepresentableContext<PositionableScrollView<Content>>) {
// Do nothing at the moment.
}
}
The view controller:
final class PositionableScrollViewVC<Content>: UIViewController where Content: View {
var scrollView: UIScrollView = UIScrollView()
var contentView: UIView!
var contentVC: UIViewController!
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
self.view.addSubview(self.scrollView)
self.scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
self.scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
self.scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
self.scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
}
override func viewDidLoad() {
super.viewDidLoad()
debugPrint("self:", self.frame())
debugPrint("self.view:", self.view!.frame)
debugPrint("self.view.subviews:", self.view.subviews)
// debugPrint("self.view.subviews[0]:", self.view.subviews[0])
// debugPrint("self.view.subviews[0].subviews:", self.view.subviews[0].subviews)
}
func add(#ViewBuilder content: #escaping () -> Content) {
self.contentVC = UIHostingController(rootView: content())
self.contentView = self.contentVC.view!
self.scrollView.addSubview(contentView)
self.contentView.leadingAnchor.constraint(equalTo: self.scrollView.leadingAnchor).isActive = true
self.contentView.trailingAnchor.constraint(equalTo: self.scrollView.trailingAnchor).isActive = true
self.contentView.topAnchor.constraint(equalTo: self.scrollView.topAnchor).isActive = true
self.contentView.bottomAnchor.constraint(equalTo: self.scrollView.bottomAnchor).isActive = true
self.contentView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.widthAnchor.constraint(greaterThanOrEqualTo: self.scrollView.widthAnchor).isActive = true
}
}
extension PositionableScrollViewVC: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<PositionableScrollViewVC>) -> PositionableScrollViewVC {
let vc = PositionableScrollViewVC()
return vc
}
func updateUIViewController(_ uiViewController: PositionableScrollViewVC, context: UIViewControllerRepresentableContext<PositionableScrollViewVC>) {
// Do nothing at the moment.
}
}
The callers:
struct TimelineView: View {
#State private var posX: CGFloat = 0
var body: some View {
GeometryReader { geo in
VStack {
Text("\(self.posX) || \(geo.frame(in: .global).width)")
PositionableScrollView() {
VStack {
Spacer()
Text("Hallo")
.background(Color.yellow)
Spacer()
}
.frame(width: 1000, height: 200, alignment: .bottomLeading)
}
.background(Color.blue)
}
}
}
}
struct TimelineView_Previews: PreviewProvider {
static var previews: some View {
TimelineView()
}
}
The display, when run, and in debugger:

Resources