I am developing an app using SwiftUI. The app is based around a NavigationView.
I am using a third-party framework that provides UIKit components and the framework has not been updated to support SwiftUI yet.
One framework method is expecting a parameter of type UINavigationController
How can I supply this framework the NavigationController created by SwiftUI ? Or how can I create a UINavigationController that will replace SwiftUI's default ?
I read https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit and https://sarunw.com/posts/uikit-in-swiftui but these seems to address another question : they explain how to use UIKit components in a SwiftUI app. My problem is the other way around, I want to use SwiftUI App and access underlying NavigationController object.
[UPDATE]
The code implementing my solution is available from this workshop : https://amplify-ios-workshop.go-aws.com/30_add_authentication/20_client_code.html#loginviewcontroller-swift
Thanks to Yonat's explanation I understood how to do this and here is my solution, hoping it will help others.
Part 1 : The UI View Controller that will be used from Swift UI. It calls a third-party authentication library, passing the UINavigationControler as parameter. The UINavigationController is an empty view, just there to allow the third-party authentication library to have a Navigation Controller to pop up the Login Screen.
struct LoginViewController: UIViewControllerRepresentable {
let navController = UINavigationController()
func makeUIViewController(context: Context) -> UINavigationController {
navController.setNavigationBarHidden(true, animated: false)
let viewController = UIViewController()
navController.addChild(viewController)
return navController
}
func updateUIViewController(_ pageViewController: UINavigationController, context: Context) {
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject {
var parent: LoginViewController
init(_ loginViewController: LoginViewController) {
self.parent = loginViewController
}
}
func authenticate() {
let app = UIApplication.shared.delegate as! AppDelegate
let userData = app.userData
userData.authenticateWithDropinUI(navigationController: navController)
}
}
Part 2 : The Swift UI View is displaying the (empty) UINavigationControler and overlays a SwiftUI view on top of it.
import SwiftUI
struct LandingView: View {
#ObservedObject public var user : UserData
var body: some View {
let loginView = LoginViewController()
return VStack {
// .wrappedValue is used to extract the Bool from Binding<Bool> type
if (!$user.isSignedIn.wrappedValue) {
ZStack {
loginView
// build your welcome view here
Button(action: { loginView.authenticate() } ) {
UserBadge().scaleEffect(0.5)
}
}
} else {
// my main app view
// ...
}
}
}
}
I don't think you can do that right now. Looking at the view debugger for NavigationView I get the image below.
So it seems to you will have to go the other way around:
Start with a UINavigationController, and wrap the SwiftUI view(s) in UIHostingController.
I tried to do the same thing because I wanted to make the interactivePopGestureRecognizer work on the whole view.
I managed to access the current navigation controller using an UINavigationController extension and overriding viewDidAppear, checking if the interactivePopGestureRecognizer was enabled and changed it ( https://stackoverflow.com/a/58068947/1745000)
At the end my effort was pointless. When the navigation view presented the DetailHostingController, it toggled off interactivePopGestureRecognizer.isEnabled!
The hosting view via topViewController.view does contain a gesture recogniser of private type SwiftUI.UIGestureRecognizer. No targets are set though...
Embedding a traditional UINavigationController may also be preferred because navigation view's own pop gesture isn't cancellable (if you drag the view a little bit and stop, it snaps back and then dismiss the detail view.
Related
I have an app already that I was able to build completely with SwiftUI.
I was using Firebase for authentication and notifications using Cloud Functions.
Now with the new SwiftUI App->Scene->View construct, I am unable to add the setup to my app.
For example -> The initial FirebaseApp.configure() would initially go in didFinishLaunchingWithOptions in AppDelegate, now I am at a loss of where to add this configuration.
Same goes with setting up remote notifications.
PS: Please comment if more details/code of the previous app is required. I did not add any code, cause I felt it was unnecessary.
There are three approaches for initialising third part frameworks in the new SwiftUI life cycle:
Using the old life cycle model
You can still use the old life cycle model:
Option 1: Use the UIKit App Delegate life cycle
When creating a new SwiftUI project, you can choose the old life cycle model. This will create an AppDelegate and a SceneDelegate as before. Not as fancy as using SwiftUI all the way, I admit - but definitely the easiest and most straightforward way.
Using the new life cycle model
If you want to use the new life cycle model, use either one of the following approaches.
Option 2: Use the App's initialiser
You can override the default initialiser of your App class, like this:
import SwiftUI
import Firebase
#main
struct SO62626652_InitialiserApp: App {
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Option 3: Use # UIApplicationDelegateAdaptor
In you App class, define a property that holds a reference to your AppDelegate, and let SwiftUI inject the AppDelegate using the # UIApplicationDelegateAdaptor property wrapper, like this:
import SwiftUI
import Firebase
#main
struct SO62626652_AppDelegateAdaptorApp: App {
#UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
Found the answer on the link below:
hackingWithSwift
The code from the page is below:
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print("Your code here")
FirebaseApp.configure()
return true
}
}
And inside the App
we need to add the below line:
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate.
The explanation is on the link.
Its possible to use Hoc with context api inside a next page?
I have a next page generated by SSR, and a HOC privateRoute to validate authorization on this page. But for every access, we have a authorization request and its sound's me like a problem.
My idea is to use contexApi to get data one time, and reuse that on auth private route.
Anyone has a minimal exemple about?
Thanks.
I found a solution.
The problem was to use Context inside react class component.
mport React, { Component } from 'react'
import UserContext from './UserContext'
class HomePage extends Component {
static contextType = UserContext
componentDidMount() {
const user = this.context
console.log(user) // { name: 'Tania', loggedIn: true }
}
for more, see https://www.taniarascia.com/using-context-api-in-react/
render() {
return <div>{user.name}</div>
}
}
What I'm looking for is a way to dismiss the keyboard while maintaining focus on the Entry field so an external keyboard can be used without giving up all that screen real estate.
I can't seem to implement the code in How to dismiss keyboard on button press in Xamarin Forms I'm calling it in the OnFocused of an Entry field.
Android.App.Application.Context as Activity;
is returning null Without the "as activity it works just fine, but I can't run
.CurrentFocus?.WindowToken;
on a Context.
This is what my code looks like.
[assembly: Dependency (typeof (KeyboardInteractions))] namespace namespace.Droid
{
public class KeyboardInteractions : IKeyboardInteractions
{
public void HideKeyboard()
{
var activity = Android.App.Application.Context as Activity;
var token = activity?.CurrentFocus?.WindowToken;
var i = (InputMethodManager)Android.App.Application.Context.GetSystemService(Context.InputMethodService);
if (token != null) {
i.HideSoftInputFromWindow(token, 0);
}
}
}
}
Use the CurrentActivityPlugin.
Once you have the plugin setup you can use it like this :
var activity = CrossCurrentActivity.Current?.Activity;
In case of queries feel free to revert.
I have a cross-platform mobile app (Android/iOS) which implements the generic WebView control. This works well for most circumstances, but some iOS users complain that, when attempting to load a certain resource-intensive web page, the app "goes black" and then focus returns to the main menu view. My suspicion is that the app is choking due to the amount of content and processing overhead of the web page, but frankly this is a blind guess and I don't have the resources (such as an iPhone at my disposal) in order to verify this. Using an iPhone simulator on a Mac does not reproduce the "black screen" issue.
Therefore, I am attempting to implement in parallel WkWebView for iOS devices at version 8.0 and above as this is presumably more performant and might alleviate the problem. It is just about working, but there seems to be a disconnect between the ViewController and ContentPage which is supposed to host the WkWebView control which I have been unable to rectify.
Below is the general implementation:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class WkWebViewPage : ContentPage
{
public WkWebViewPage (string url, string title)
{
InitializeComponent();
Title = title;
App.GetWkWebView(this, url);
}
}
The markup for WkWebPageView has no inner content.
The method App.GetWkWebView is a delegate of type Action implemented as a static property in the main App class. This is assigned in the FinishedLaunching method of the AppDelegate class (iOS project) to a static method in the static class I am using to manage invoking the WkWebView. This class is implemented as such:
public static class WkWebViewController
{
private static WKWebView _wkWebView;
public static void GetWkWebView(Page parentView, string url)
{
if(_wkWebView == null)
{
// INSERT ATTEMPTED APPROACHES BELOW HERE
var frame = view.Frame;
var cgRect = new CoreGraphics.CGRect(frame.X, frame.Y, frame.Width, frame.Height);
_wkWebView = new WKWebView(cgRect, new WKWebViewConfiguration());
view.AddSubview(_wkWebView);
// NavigationDelegate is a custom class; not germane to the issue
_wkWebView.NavigationDelegate = new NavigationDelegate();
}
var nsUrl = new NSUrl(url);
var request = new NSUrlRequest(nsUrl);
_wkWebView.LoadRequest(request);
}
}
Here is where the trouble begins. I have tried two approaches to obtaining the appropriate ViewController -- and more pertinently, the UIView object:
1)
var renderer = Platform.GetRenderer(parentView);
if (renderer == null)
{
renderer = Platform.CreateRenderer(parentView);
Platform.SetRenderer(parentView, renderer);
}
var view = renderer.ViewController.View;
This results in the following:
(source: aquaspy.com)
The content area is white/blank. The http request is submitted successfully as a 200 response is received. Note that the navigation bar above the content area properly displays.
2)
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
var view = vc.View;
Which results in:
(source: aquaspy.com)
In this case, the web page displays; however, the WkWebView control takes up the entire screen, obscuring the navigation bar (and seemingly the Status Bar).
Any suggestions would be greatly appreciated!
Why do I need so much (seemingly useless) pass through code that objc did not
require in a similar derivation:
class myNavigationController: UINavigationController {
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
...
repeat ad nauseam for every single view controller of mine loaded from xib.
This should not be an issue if your don't want to overload the designated initializer init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) your UINavigationController subclass. If you just want to make use of the default (super) initializer, you can remove both those methods from your class.
I.e., the following class
// MyNavigationController.swift
import UIKit
class MyNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
// I don't want to make use of this ...
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// ... nor this
}
// Things I do want to do with my nav. controller
}
can be reduced to
// MyNavigationController.swift
import UIKit
class MyNavigationController: UINavigationController {
// Things I do want to do with my nav. controller
}
Without any errors. (Verified in Swift 2.0, Xcode 7.2, simulator: iOS 9.2). This is expected behavior, see e.g. the accepted answer in thread 'required' initializer 'init(coder:)' must be provided by subclass of 'UITableViewCell'`.
If you still get an error when removing these for this subclass type, please give some details regarding your use of the class / Xcode version etc.