I have a widget with a link. I'm implementing the widget URL in SceneDelegate. The problem is when I tap on the widget I get a link, it works. But when I call View Controller function in SceneDelegate, it does nothing. Well, it can print something, but it doesn't change anything. SceneDelegate:
// App launched
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
window.rootViewController = ViewController()
self.window = window
window.makeKeyAndVisible()
getURL(urlContext: connectionOptions.urlContexts)
}
// App opened from background
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
getURL(urlContext: URLContexts)
}
// Get URL
private func getURL(urlContext: Set<UIOpenURLContext>) {
guard let urlContexts = urlContext.first(where: { $0.url.scheme == "app-deeplink" }) else { return }
print(urlContexts.url)
print("success")
let viewController = ViewController()
viewController.cameFromWidget()
}
Function in ViewController that changes labels text:
func cameFromWidget() {
label.text = "Hello"
print(label.text)
}
WidgetExtension:
var body: some View {
Text(entry.date, style: .time)
.widgetURL(URL(string: "app-deeplink://app"))
}
So ViewController func just prints the text but doesn't change it when I call it from SceneDelegate.
My widget link is only in WidgetExtension and SceneDelegate, I didn't add it to infoPlist.
My question: Why does it work but does nothing? Maybe I should add it to some file?
Thank you so much!
I found out. To make some changes in your viewController you need to make it with rootViewController:
private func getURL(urlContext: Set<UIOpenURLContext>) {
guard let urlContexts = urlContext.first(where: { $0.url.scheme == "app-deeplink" }) else { return }
print(urlContexts.url)
print("success")
let rootViewController = window?.rootViewController as? ViewController
rootViewController?.cameFromWidget()
}
Related
How can I hide the Title Bar in the new SwiftUI App Protocol?
Since the AppDelegate.swift and SceneDelegate.swift protocols are gone, I cant follow this documentation anymore:
https://developer.apple.com/documentation/uikit/mac_catalyst/removing_the_title_bar_in_your_mac_app_built_with_mac_catalyst
I can't implement this code:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
#if targetEnvironment(macCatalyst)
if let titlebar = windowScene.titlebar {
titlebar.titleVisibility = .hidden
titlebar.toolbar = nil
}
#endif
}
}
Hope it's still possible with the new AppProtocol..
Thanks in advance
This is how to hide the titlebar:
struct ContentView: View {
var body: some View {
ZStack {
Text("Example UI")
}
.withHostingWindow { window in
#if targetEnvironment(macCatalyst)
if let titlebar = window?.windowScene?.titlebar {
titlebar.titleVisibility = .hidden
titlebar.toolbar = nil
}
#endif
}
}
}
extension View {
fileprivate func withHostingWindow(_ callback: #escaping (UIWindow?) -> Void) -> some View {
self.background(HostingWindowFinder(callback: callback))
}
}
fileprivate struct HostingWindowFinder: UIViewRepresentable {
var callback: (UIWindow?) -> ()
func makeUIView(context: Context) -> UIView {
let view = UIView()
DispatchQueue.main.async { [weak view] in
self.callback(view?.window)
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
On your Scene, set .windowStyle(_:) to HiddenTitleBarWindowStyle().
#main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.windowStyle(HiddenTitleBarWindowStyle())
}
}
EDIT: Ah crap. While this API is supposedly available for Mac Catalyst according to the online documentation, it looks like it’s not actually marked as such in frameworks so you can’t use it.
I have this iOS app which trying to downgrade to support older iPhones with iOS version 11.0, but ran into few problems.
I have the below line of code in AppDelegate which shows error 'connectedScenes' is only available in iOS 13.0 or newer and 'UIWindowScene' is only available in iOS 13.0 or newer
var keyWindowDev = UIApplication.shared.connectedScenes.filter({$0.activationState == .foregroundActive}).map({$0 as? UIWindowScene}).compactMap({$0}).first?.windows.filter({$0.isKeyWindow}).first
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,UIWindowSceneDelegate, UNUserNotificationCenterDelegate,MessagingDelegate {
var window: UIWindow?
//....
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
}
}
//Below is keyWindowDev usage in AppDelegate
extension UIApplication {
class func topViewController(base: UIViewController? = keyWindowDev?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
return base
}
}
And keyWindowDev is also used to set SVProgressHUD Container View around the app
class HomeVC: UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
SVProgressHUD.setContainerView(keyWindowDev)
}
}
Please how to change the line of code in keyWindowDev to support iOS 11?
You should be able to use something like this:
var keyWindowDev: UIWindow? = {
if #available(iOS 13.0, *) {
return UIApplication.shared.connectedScenes
.filter { $0.activationState == .foregroundActive }
.compactMap { $0 as? UIWindowScene }.first?.windows
.first(where: \.isKeyWindow)
} else {
return UIApplication.shared.keyWindow
}
}()
I made some optimizations to your original code.
I have created an environment object in my scendelegate and want to also use it in another function that resides inside my scenedelegate. The code looks as follows:
SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
//#EnvironmentObject var data: DataFetcher
var data = DataFetcher()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
let data = (UIApplication.shared.delegate as! AppDelegate).self.data
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(data))
self.window = window
window.makeKeyAndVisible()
}
}
func handleIncomingDynamicLink(_ dynamicLink: DynamicLink){
guard let url = dynamicLink.url else {
print("That's weird. My dynamic link object has no url")
return
}
print("Your incoming link parameter is \(url.absoluteString)")
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems else {return}
self.data.linkRecieved = true
print(data.linkRecieved)
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let incomingURL = userActivity.webpageURL {
print("Incoming URL is \(incomingURL)")
_ = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { (dynamicLink, error) in
guard error == nil else{
print("Found an error! \(error!.localizedDescription)")
return
}
if let dynamicLink = dynamicLink {
self.handleIncomingDynamicLink(dynamicLink)
}
}
}
}
The problem is that the environment object's linkRecieved variable is not changed inside contentview when the handleIncomingDynamicLink function is executed (I have verified the variable is indeed changed to true once the function is run). Any insight in fixing this issue is much appreciated!!!
You have assigned a DataFetcher instance to the data property, presumably to set its type and eliminate the error that your property isn't initialised.
Then in willConnectTo you get the DataFetcher instance from your AppDelegate. Your dataproperty and the localdatavariable inwillConnectTo(That you subsequently add to the environment) now reference different instances ofDataFetcher`, but you didn't realise.
I am not a big fan of assigning "throw away" instances to properties for this reason.
A better approach is to use an implicitly unwrapped optional. That way if you don't assign a value to the property you will get a crash and quickly work out that something isn't right.
Then in willConectTo you can assign the instance from your AppDelegate to the property and put it in the environment.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
//#EnvironmentObject var data: DataFetcher
var data: DataFetcher!
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
self.data = (UIApplication.shared.delegate as! AppDelegate).self.data
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(data))
self.window = window
window.makeKeyAndVisible()
}
}
You need to inject that var data which is property, so remove the following line
let window = UIWindow(windowScene: windowScene)
//let data = (UIApplication.shared.delegate as! AppDelegate).self.data // << this one
window.rootViewController = UIHostingController(rootView:
ContentView().environmentObject(self.data)) // << inject own property
I have a (Catalyst) app, that is able to have several windows. I created a button and if the user presses this button, the app creates a new window. To know, what View needs to be shown, I have different NSUserActivity activityTypes. In the new window there should be a button, that can close this new window. The problem is, that on looping through the open sessions, all the userActivities are nil (and I need the correct UISceneSession for the UIApplication.shared.requestSceneSessionDestruction.
This is my code:
struct ContentView: View {
var body: some View {
VStack {
// Open window type 1
Button(action: {
UIApplication.shared.requestSceneSessionActivation(nil,
userActivity: NSUserActivity(activityType: "window1"),
options: nil,
errorHandler: nil)
}) {
Text("Open new window - Type 1")
}
// Open window type 2
Button(action: {
UIApplication.shared.requestSceneSessionActivation(nil,
userActivity: NSUserActivity(activityType: "window2"),
options: nil,
errorHandler: nil)
}) {
Text("Open new window - Type 2")
}
}
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
if connectionOptions.userActivities.first?.activityType == "window1" {
window.rootViewController = UIHostingController(rootView: Window1())
} else if connectionOptions.userActivities.first?.activityType == "window2" {
window.rootViewController = UIHostingController(rootView: Window2())
} else {
window.rootViewController = UIHostingController(rootView: ContentView())
}
self.window = window
window.makeKeyAndVisible()
}
}
...
struct Window1: View {
var body: some View {
VStack {
Text("Window1")
Button("show") {
for session in UIApplication.shared.openSessions {
// this prints two times nil !
print(session.scene?.userActivity?.activityType)
}
}
}
}
}
struct Window2: View {
var body: some View {
Text("Window2")
}
}
I ran into the same issue. I ended up assigning unique delegate classes to each scene and checking that instead.
The current situation is I have an initial view of login (A) and a second view when (B) has already logged in.
I want that when I open the application and the user has the values stored in the UserDefaults.standard, check that the values match those of the server and if they are correct enter the B view.
The problem is that alamofire is asynchronous, and the A-view is loaded before you get the answer.
SceneDelegate.swift
First attempt before we knew that alamofire was launched asynchronously
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//Code to open default viewA
...
//Code to try open viewB
let logAcepted = openViewB(url, param)
if logAcepted //<-- this execute before query in func before
{
if let windowScene = scene as? UIWindowScene
{
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: Principal())
self.window = window
window.makeKeyAndVisible()
}
}
}
I have seen that there is a completion field that could help to solve this problem but I think that I have not applied it well.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//Code to open default viewA
...
//Code to try open viewB
openViewB(url, param, completion: {logAcepted in
if logAcepted
{
if let windowScene = scene as? UIWindowScene
{
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: Principal())
self.window = window
window.makeKeyAndVisible()
}
}
})
}
How can I make this function run synconically? or have the main code wait for the result of the query?
func openViewB(_ url: String, _ param: [String : String], completion : #escaping (Bool)->()) {
var ret : Bool = false
AF.request(url, parameters: param).responseJSON {response in
switch response.result {
case .success(let value):
if let JSON = value as? [String: Any]
{
let status = JSON["status"] as! String
switch status
{
case "Ok":
ret = true
completion(ret)
default:
ret = false
completion(ret)
}
}
case .failure( _):
ret = false
completion(ret)
}
}
}
I found a lot of related information but none that works in this version of swift/alamofire
Don't wait, never wait.
Your openViewB method has already a completion handler, use it.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
//Code to open default viewA
...
//Code to try open viewB
openViewB(url, param) { logAccepted in
if logAccepted
{
if let windowScene = scene as? UIWindowScene
{
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: Principal())
self.window = window
window.makeKeyAndVisible()
}
}
}
}