Can't open Deeplinks with different tails Widgets SwiftUI [duplicate] - swiftui

I have a simple widget (medium-sized) with two texts, and what I want is to be able to perform a deep link to lead the user to a specific section of my app, but I can't seem to find a way to do so.
The view I have written (which is very simple):
HStack {
Text("FIRST ITEM")
Spacer()
Text("SECOND ITEM")
}
I have already tried to replace
Text("SECOND ITEM")
with
Link("SECOND ITEM destination: URL(string: myDeeplinkUrl)!)
but it doesn't work either.

In the Widget view you need to create a Link and set its destination url:
struct SimpleWidgetEntryView: View {
var entry: SimpleProvider.Entry
var body: some View {
Link(destination: URL(string: "widget://link1")!) {
Text("Link 1")
}
}
}
Note that Link works in medium and large Widgets only. If you use a small Widget you need to use:
.widgetURL(URL(string: "widget://link0")!)
In your App view receive the url using onOpenURL:
#main
struct WidgetTestApp: App {
var body: some Scene {
WindowGroup {
Text("Test")
.onOpenURL { url in
print("Received deep link: \(url)")
}
}
}
}
It is also possible to receive deep links in the SceneDelegate by overriding:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>)
You can find more explanation on how to use this function in this thread:
Detect app launch from WidgetKit widget extension
Here is a GitHub repository with different Widget examples including the DeepLink Widget.

Also, you can do it using AppDelegate (if you not using SceneDelegate):
.widgetURL(URL(string: "urlsceheme://foobarmessage"))
// OR
Link(destination: URL(string: "urlsceheme://foobarmessage")!) {
Text("Foo")
}
Set this code within AppDelegate
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
let message = url.host?.removingPercentEncoding // foobarmessage
return true
}

See docs on: Respond to User Interactions
When users interact with your widget, the system launches your app to handle the request. When the system activates your app, navigate to the details that correspond to the widget’s content. Your widget can specify a URL to inform the app what content to display. To configure custom URLs in your widget:
For all widgets, add the widgetURL(_:) view modifier to a view in your widget’s view hierarchy. If the widget’s view hierarchy includes more than one widgetURL modifier, the behavior is undefined.
For widgets that use WidgetFamily.systemMedium or WidgetFamily.systemLarge, add one or more Link controls to your widget’s view hierarchy. You can use both widgetURL and Link controls. If the interaction targets a Link control, the system uses the URL in that control. For interactions anywhere else in the widget, the system uses the URL specified in the widgetURL view modifier.
For example, a widget that displays details of a single character in a game can use widgetURL to open the app to that character’s detail.
#ViewBuilder
var body: some View {
ZStack {
AvatarView(entry.character)
.widgetURL(entry.character.url)
.foregroundColor(.white)
}
.background(Color.gameBackground)
}
If the widget displays a list of characters, each item in the list can be in a Link control. Each Link control specifies the URL for the specific character it displays.
When the widget receives an interaction, the system activates the containing app and passes the URL to onOpenURL(perform:), application(_:open:options:), or application(_:open:), depending on the life cycle your app uses.
If the widget doesn’t use widgetURL or Link controls, the system activates the containing app and passes an NSUserActivity to onContinueUserActivity(_:perform:), application(_:continue:restorationHandler:), or application(_:continue:restorationHandler:). The user activity’s userInfo dictionary contains details about the widget the user interacted with. Use the keys in WidgetCenter.UserInfoKey to access these values from Swift code. To access the userInfo values from Objective-C, use the keys WGWidgetUserInfoKeyKind and WGWidgetUserInfoKeyFamily instead.

Related

How to prevent scrolling when interacting with a PKCanvasView in a Scroll view in SwiftUI?

I have a UIViewRepresentable that represents a PKCanvasView.
struct PKCanvasRepresentable : UIViewRepresentable
{
#Binding var canvas: PKCanvasView
func makeUIView(context: Context) -> PKCanvasView {
canvas.tool = PKInkingTool(.pen, color: .black, width: 2)
canvas.drawingPolicy = .anyInput
canvas.isOpaque = false
canvas.backgroundColor = .clear
return canvas
}
func updateUIView(_ uiView: PKCanvasView, context: Context) {}
}
I want to use it as part of a sheet, that contains other input components and must be vertically scrollable.
#State var canvas = PKCanvasView()
var body: some View {
NavigationView {
ScrollView {
VStack {
// ..various components..
PKCanvasRepresentable(canvas: $canvas)
}
}
}
}
The drawing does not work, because the drawing gesture gets canceled by the scroll gesture.
I would like the PKCanvasView related gestures having priority over the scroll view ones. How can i achieve this?
Example
The expected behaviour can be seen when - for example - a DatePicker in wheel style is in a ScrollView. The scroll view does not receive any gesture input when interacting with the DatePicker. I would like to have the same behaviour for a PKCanvasView.
Additional info
I tried to add various Gesture modifiers to the Representable which prevents the ScrollView from getting input events - but of course that also prevents the Canvas from getting user input.
I built a drawing component myself in the past which worked, because i had control over the Gestures that were added to make the component happen. But i would prefer to use PKCanvasView, which does everything i need already - except from the described issue.
I saw this question - but it has nothing to do with PKCanvasView and its solution does not help.
I tried ai based code generators - but i don't have any subscriptions so i'm limited in tries and length of the answer. I tried the following quote, which produced only invalid, tutorial level answers:
Write a SwitUI view that contains of a ScrollView which has a nested UIViewRepresentable of a PKCanvasView where the ScrollView does not receive any kind of user input, events or gestures while the user interacts with the PKCanvasView and tries to draw but functions normally whenever the user does not interact with the PKCanvasView

SwiftUI Dismiss Keyboard from UITextField

So for my own reasons, I need the full control that UITextField and its delegates would normally offer, but the screen it's being added to is written with SwiftUI. Needs a .decimal keyboard, so no Return Key.
I have only 2 issues remaining, 1 of which I hope to resolve here. Attempting to add a call to resign first responder and add it to a VStack on the page basically disables the UITextField, since I can't bring up the keyboard.
How can I dismiss this keyboard without adding an arbitrary extra button to the page?
Example Code:
Struct CustomTextView: UIViewRepresentable {
/// Insert init, updateView, binding variable, coordinator, etc
func makeView() -> UITextField {
var textField = UITextField()
textField.delegate = context.coordinator
/// Set up rest of textfield parameters such as Font, etc.
return textField
}
}
extension CustomTextView {
class Coordinator: NSObject, UITextFieldDelegate {
/// UITextfield delegate implementations, extra reference to binding variable, etc
/// Primarily textField.shouldChangeCharactersInRange
}
}
struct ContentView: View {
#State viewModel: ViewModel
var body: some View {
VStack {
CustomTextView($viewModel.parameter)
/// Other views
}
.onTap {
/// Attempting to add the generic call to UIApplication for resignFirstResponder here does not allow CustomTextView to ever hold it even when tapped in
}
}
}
I can't give all the code for privacy reasons, but this should be enough to establish my issue.
I have done this by adding this Function to you view below.
func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
Then with a ontapGesture you can make the keyboard go away.
For example you can use this on the background Stack of your whole view. If a user taps on the background the keyboard will dissapear.
.onTapGesture {
self.hideKeyboard()
}
So I found a trick on my own with an epiphany overnight.
First, I would like to share to anyone else a very basic reason why inb4cookies solution wasn't quite adequate. While I had already tried adding a resignFirstResponder call like it to the onTap of the background stack, it was triggering the onTap for the VStack when I was clicking the field.
This is likely because I am using a UITextField as the back end for this component and not a SwiftUI TextField.
However, it was partially used in the final solution. I still applied it, but there is an extra step.
VStack {
CustomTextView($viewModel.parameter)
.onTap {/*Insert literally any compiling code here*/ }
/// Other views
}
.onTap {
self.hideKeyboard()
}
You'll see that above, there is an extra onTap. I tested it with a print statement, but this will override the onTap for the VStack and prevent the keyboard from being dismissed right after it is brought up. Tapping anywhere else on the VStack still closes it, except for Buttons. But I can always add hideKeyboard to those buttons if needed.

Can a NavigationLink perform an async function when navigating to another view?

I'm trying to create a navigation link in SwiftUI that logs in a user and navigates to the next screen.
I've tried using .simultaneousGesture as shown below, based on this solution. When I have it perform a simple action (e.g. print("hello")), the code works fine and navigates to the next page, but when I have it perform my authState.signUp function (which is async), it calls the function but doesn't navigate to the next page.
Is there a different way I should be approaching this?
NavigationLink(destination: NextView()) {
Text("Create account")
}
.simultaneousGesture(TapGesture().onEnded {
authState.signUp(user: user)
})
you can use a selection arg of navigationLink
add this var
#State var trigger: Bool? = nil
and use
NavigationLink(destination: NextView(), tag: true,
selection: $trigger ) {
}
when ever your other job (login) is done toggle() the trigger and navigationLink fires.

In a SwiftUI AppLifecycle Document App, how can I get a menu command in active ContentView?

I'm writing a MacOS document app using the SwiftUI App lifecycle, and all the tricks I see here and elsewhere for sending a menu action to the active window depend on using platform specific implementation, which is (mostly) unavailable in a SwiftUI Lifecycle app. What I'm looking for is something like SideBarCommands(), which adds a menu item that, when selected by mouse or command key, toggles the appearance of the sidebar in the active window. All the Command examples I have seen thus far are trivial, none address a multi-document, multi-window use case.
Given a ContentView declared thusly:
struct ContentView: View {
#Binding var document: TickleDocument
var body: some View {
TextEditor(text: $document.text)
}
public func menuTickle() {
document.text = "Wahoo!"
}
}
and a command, which is added via:
struct TickleApp: App {
public static var target:TickleDocument?
var body: some Scene {
let docGroup = DocumentGroup(newDocument: TickleDocument()) { file in
ContentView(document: file.$document)
}
docGroup
.commands {
CommandMenu("App Tickles") {
Button("Tickle The ContentView") {
// Here's where I need to call menuTickle() on the active ContentView
}.keyboardShortcut("t")
}
}
}
}
}
What do I need to do so the button closure can call menuTickle() on the active ContentView? I know it can be done, because SideBarCommands() does it (unless Apple is using some non-public API to do it...).
For bonus points, tell me how I can detect whether or not I'm the active ContentView while body is being evaluated, and how I can detect when it changes! Tracking the Environment variable scenePhase is worthless - it always reports active, and never changes.
My question is a duplicate of this one.
The answer to that question contains a link to a solution that I have verified works, and can be found here

Is it possible to make a modal non-dismissible in SwiftUI?

I am creating an App where the login / register part is inside a modal, which is shown if the user is not logged in.
The problem is, that the user can dismiss the modal by swiping it down...
Is it possible to prevent this?
var body: some View {
TabView(selection: $selection) {
App()
}.sheet(isPresented: self.$showSheet) { // This needs to be non-dismissible
LoginRegister()
}
}
Second example:
I am using a modal to ask for information. The user should not be able to quit this process except by dismissing the modal with save button. The user has to input information before the button works. Unfortunately the modal can be dismissed by swiping it down.
Is it possible to prevent this?
iOS 15 and later:
Use .interactiveDismissDisabled(true) on the sheet, that's all.
Prev iOS 15:
You can try to do this by using a highPriorityGesture. Of course the blue Rectangle is only for demonstration but you would have to use a view which is covering the whole screen.
struct ModalViewNoClose : View {
#Environment(\.presentationMode) var presentationMode
let gesture = DragGesture()
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(width: 300, height: 600)
.highPriorityGesture(gesture)
.overlay(
VStack{
Button("Close") {
self.presentationMode.value.dismiss()
}.accentColor(.white)
Text("Modal")
.highPriorityGesture(gesture)
TextField("as", text: .constant("sdf"))
.highPriorityGesture(gesture)
} .highPriorityGesture(gesture)
)
.border(Color.green)
}
}
This is a common problem and a "code smell"... well not really code but a "design pattern smell" anyway.
The problem is that you are making your login process part of the rest of the app.
Instead of presenting the LoginRegister over the App you should really be showing either App or LoginRegister.
i.e. you should have some state object like userLoggedIn: Bool or something and depending on that value you should show either App or LoginRegister.
Just don't have both in the view hierarchy at the same time. That way your user won't be able to dismiss the view.
If you dont mind using Introspect:
import Introspect
#available(iOS 13, *)
extension View {
/// A Boolean value indicating whether the view controller enforces a modal behavior.
///
/// The default value of this property is `false`. When you set it to `true`, UIKit ignores events
/// outside the view controller's bounds and prevents the interactive dismissal of the
/// view controller while it is onscreen.
public func isModalInPresentation(_ value: Bool) -> some View {
introspectViewController {
$0.isModalInPresentation = value
}
}
}
Usage:
.sheet {
VStack {
...
}.isModalInPresentation(true)
}
iOS 15+
Starting from iOS 15 you can use interactiveDismissDisabled.
You just need to attach it to the sheet:
var body: some View {
TabView(selection: $selection) {
App()
}.sheet(isPresented: self.$showSheet) {
LoginRegister()
.interactiveDismissDisabled(true)
}
}
Regarding your second example, you can pass a variable to control when the sheet is disabled:
.interactiveDismissDisabled(!isAllInformationProvided)
You can find more information in the documentation.
theoretically this may help you (I didn't tryed it)
private var isDisplayedBind: Binding<Bool>{ Binding(get: { true }, set: { _ = $0 }) }
and usage:
content
.sheet(isPresented: isDisplayedBind) { some sheet }