Unable to get RootViewController in Xamarin iOS - xamarin

I created app, which Authenticate using Azure AD
In Android it is working fine, but in iOS, it need RootViewController for load the page. But UIApplication.SharedApplication.KeyWindow is null. So I am not able to get UIApplication.SharedApplication.KeyWindow.RootViewController
Bellow is the code:
var authResult = await authContext.AcquireTokenAsync(
graphResourceUri,
ApplicationID,
new Uri(returnUri),
new PlatformParameters(UIApplication.SharedApplication.KeyWindow.RootViewController)
);
Any other way from which I can get RootViewController

This looks stupid but works.
UIWindow window = UIApplication.SharedApplication.KeyWindow;
UIViewController presentedVC = window.RootViewController;
while (presentedVC.PresentedViewController != null)
{
presentedVC = presentedVC.PresentedViewController;
}

I tried this code also, but it is not working.
I got the root cause of this issue. Issue was, when I am going to access the RootViewController then there should be at least one page Initialized, but it is not Initialized so I am unable to get RootViewController
so I gave daily to Initialized the page then I got the RootViewController

Access RootViewController after your window is actually created.
Do this after base.FinishedLauching, like this:
var result = base.FinishedLaunching(app, options);
var platformParameters = UIApplication.SharedApplication.KeyWindow.RootViewController;
App.AuthenticationClient.PlatformParameters = new PlatformParameters(platformParameters);
return result;

Depending on the type of window you have loaded getting the RootViewController can be problematic. This version has been the most stable one I've tried thus far and avoids tail recursive loops.
public UIViewController GetRootController(){
var root = UIApplication.SharedApplication.KeyWindow.RootViewController;
while (true)
{
switch (root)
{
case UINavigationController navigationController:
root = navigationController.VisibleViewController;
continue;
case UITabBarController uiTabBarController:
root = uiTabBarController.SelectedViewController;
continue;
}
if (root.PresentedViewController == null) return root;
root = root.PresentedViewController;
}
}

Related

MvvmCross migration causing a Xamarin Custom iOS View Presenter issue

While creating a CustomIosViewPresenter (of type MvxIosViewPresenter), in MVVMCross 5.x, there was a Show override that I was able to use to get the IMvxIosView so as to update the UIViewController presentation style using the PresentationValues from the ViewModel.
I had this code and it worked:
// Worked before
public override void Show(IMvxIosView view, MvvmCross.ViewModels.MvxViewModelRequest request)
{
if (request.PresentationValues != null)
{
if (request.PresentationValues.ContainsKey("NavigationMode") &&
request.PresentationValues["NavigationMode"] == "WrapInModalWithNavController")
{
var vc = view as IModalPresentation;
vc.ModalPresentationAttribute = new MvxModalPresentationAttribute
{
WrapInNavigationController = true,
ModalPresentationStyle = UIModalPresentationStyle.OverFullScreen,
ModalTransitionStyle = UIModalTransitionStyle.CoverVertical
};
}
}
base.Show(view, request);
}
But after migrating to MvvmCross 7.1, the older override doesn't work anymore and I have to use this instead, but there is no view passed into the Show override, how do I get it?
I tried this code below, but view is null and it's not able to cast it this way var view = viewType as IMvxIosView;
// Doesn't work now
public override Task<bool> Show(MvxViewModelRequest request)
{
if (request.PresentationValues != null)
{
if (request.PresentationValues.ContainsKey("NavigationMode") &&
request.PresentationValues["NavigationMode"] == "WrapInModalWithNavController")
{
var viewsContainer = Mvx.IoCProvider.Resolve<IMvxViewsContainer>();
var viewType = viewsContainer.GetViewType(request.ViewModelType);
var view = viewType as IMvxIosView;
var vc = view as IModalPresentation;
vc.ModalPresentationAttribute = new MvxModalPresentationAttribute
{
WrapInNavigationController = true,
ModalPresentationStyle = UIModalPresentationStyle.OverFullScreen,
ModalTransitionStyle = UIModalTransitionStyle.CoverVertical
};
}
}
return base.Show(request);
}
The reason I need this is because without this function when I close the special flow of view controllers that need this, its not closing all the view controllers in that flow, it closes only one of them at a time.
What you would normally do with MvvmCross if you want to navigate within a Modal ViewController is firstly add a MvxModalPresentationAttribute to the modal that will host the rest of the navigation where you set WrapInNavigationController to true.
For the children, it would just be regular child navigation, no attributes needed.
If you then want to control how the modal is popping you would create your own MvxPresentationHint and register it in your presenter using AddPresentationHintHandler.
Then you would in your ViewModel where you want to change the presentation call NavigationService.ChangePresentation(your hint).
As for the Presentation Hint, it should probably just call CloseModalViewControllers and that would probably do what you want.
TLDR: Feel for the developers that will come after you and build stuff the right way
So I dug into the MvvmCross MvxIosViewPresenter source code and was able to use this new override CreateOverridePresentationAttributeViewInstance()
I needed the request object to see the presentation values so I updated the Show function that gets called before the other override as follows:
MvxViewModelRequest _request;
public override Task<bool> Show(MvxViewModelRequest request)
{
_request = request;
return base.Show(request);
}
And I was able to get the ViewController this way, in order to selectively present it as a modal:
{
var view = base.CreateOverridePresentationAttributeViewInstance(viewType);
if (_request.PresentationValues.ContainsKey("NavigationMode") &&
_request.PresentationValues["NavigationMode"] == "WrapInModalWithNavController")
{
var vc = view as IModalPresentation;
vc.ModalPresentationAttribute = new MvxModalPresentationAttribute
{
WrapInNavigationController = true,
ModalPresentationStyle = UIModalPresentationStyle.OverFullScreen,
ModalTransitionStyle = UIModalTransitionStyle.CoverVertical
};
return vc;
}
return view;
}
And then the closing of the modal was another challenge, that I was able to figure out using the TryCloseViewControllerInsideStack and ChangePresentation overrides

In Xamarin, How Do I Remove the Native iOS View Controller to Re-Expose the PCL Undearneath

I am using dependency injection to fireup Wikitude from my PCL on iOS Native Xamarin. This works fine. I added a button on Wikitude to exit and go back to the PCL - this works fine up the the point where I need to Delete or Remove the ViewController or Storyboard I originally created. Not sure how to do it..
To start Wikitude we do:
public void LaunchWikitude()
{
var storyboard = UIStoryboard.FromName("MainStoryboard_iPhone", null);
var controller = storyboard.InstantiateViewController("StoryBoardViewController") as UIViewController;
//var window = new UIWindow(UIScreen.MainScreen.Bounds);
var window = UIApplication.SharedApplication.KeyWindow;
window.RootViewController = controller;
window.MakeKeyAndVisible();
}
And to stop Wikitude and go back to the PCL (where I'm stuck):
public override void InvokedURL(WTArchitectView architectView, NSUrl url)
{
Console.WriteLine ("architect view invoked url: " + url);
//architectView.Stop();
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
}

Ripple Effect gone after adding TapGestureRecognizer to ViewCell

I added a custom LongPressGestureRecognizer to the ViewCell's root layout to handle certain cases, but after adding it, I find that the ripple effect when tapping the ViewCell is gone on Android. I tried to add back the animation by getting the native view, set background drawable to Android.Resource.Attribute.SelectableItemBackground by using below code
int[] attrs = { Android.Resource.Attribute.SelectableItemBackground };
var ta = CrossCurrentActivity.Current.Activity.ObtainStyledAttributes(attrs);
var drawable = ta.GetDrawable(0);
nativeView.SetBackgroundDrawable(drawable);
ta.Recycle();
Even this doesn't work. Any other way to make it work?
For those who want to know, I discarded the custom long press gesture recognizer way of achieving the goal, since it's the wrong way of doing things. On Android, we should use ItemLongClick event instead. Here is what I did, first, find out the native ListView through some method, my way is to first get the renderer of the ListView, then get underlying ListView. Another way is to use below code to find the ListView, but this way requires more work if you have multiple ListView
public static List<T> FindViews<T>(this ViewGroup viewGroup) where T : View
{
var result = new List<T>();
var count = viewGroup.ChildCount;
for (int i = 0; i < count; i++)
{
var child = viewGroup.GetChildAt(i);
var item = child as T;
if (item != null)
{
result.Add(item);
}
else if (child is ViewGroup)
{
var innerResult = FindViews<T>(child as ViewGroup);
if (innerResult != null)
{
result.AddRange(innerResult);
}
}
}
return result;
}
var rootView =(ViewGroup)CurrentActivity.Window.DecorView.RootView
var nativeListView = rootView.FindView<Android.Widget.ListView>();
Then override the OnAppearing method of the Page, in it, attach ItemLongClick event handler. Also override OnDisappearing method, in it, detach the ItemLongClick event handler. This is important. Simply add ItemLongClick event handler in constructor seems not working.

Test for Firefox being hidden in OS X

Is there a way I test to see if Firefox windows are hidden in OS X via the "Hide Firefox" (Command-H) option? Enumerating using nsIWindowMediator says that the hidden attribute is false and the windowState is STATE_NORMAL for these hidden windows in OS X. Thanks!
Try console.log(window) or if you dont have access to thew indow object Services.appShell.hiddenDOMWindow.console.log(Services.wm.getMostRecentWindow('navigator:browser')) then in browser console go and click '[object ChromeWindow]' and then it will print all its enumerable and non-enumerable properties.
Maybe it will show something in here differently when it's hidden I'm not sure.
i dont know but heres a way that might help you figure out.
install this addon from github
and then in scratchpad in browser environment run Cu.import('chrome://cdumpjsm/content/cDump.jsm'); cDump(window,{depth:0}) when hidden, then save that file. then show the window then run cDump again, then diff the files
Ok at long last here is your solution. js-ctypes using objective-c:
Tested on OS 10.9
GitHubGIST :: Noitidart / _ff-addon-snippet-OSXHidden.js
Cu.import('resource://gre/modules/ctypes.jsm');
var objc = ctypes.open(ctypes.libraryName('objc'));
// types
var id = ctypes.voidptr_t;
var SEL = ctypes.voidptr_t;
var BOOL = ctypes.signed_char;
// constants
var nil = ctypes.voidptr_t(0);
//common functions
var objc_getClass = objc.declare('objc_getClass', ctypes.default_abi, id, ctypes.char.ptr);
var sel_registerName = objc.declare('sel_registerName', ctypes.default_abi, SEL, ctypes.char.ptr);
var objc_msgSend = objc.declare('objc_msgSend', ctypes.default_abi, id, id, SEL, '...');
//common selectors
var alloc = sel_registerName('alloc');
var init = sel_registerName('init');
var release = sel_registerName('release');
//now using docs here: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/index.html#//apple_ref/occ/instp/NSApplication/hidden
// pool = [[NSAutoreleasePool alloc] init]
var NSAutoreleasePool = objc_getClass('NSAutoreleasePool');
var pool = objc_msgSend(objc_msgSend(NSAutoreleasePool, alloc), init);
// NSApp = [NSApplication sharedApplication];
var NSApplication = objc_getClass('NSApplication');
var sharedApplication = sel_registerName('sharedApplication');
var NSApp = objc_msgSend(NSApplication, sharedApplication);
// [NSApp isHidden]
var isHidden = sel_registerName('isHidden');
var objc_msgSend_returnBool = objc.declare('objc_msgSend', ctypes.default_abi, BOOL, id, SEL, '...'); //this is return value because `isHidden` returns a BOOL per the docs
var rez_isHidden = objc_msgSend_returnBool(NSApp, isHidden);
console.info('rez_isHidden:', rez_isHidden, rez_isHidden.toString(), uneval(rez_isHidden));
if (rez_isHidden == 0) {
console.log('Firefox is HIDDEN!');
} else if (rez_isHidden == 1) {
console.log('Firefox is showing.');
} else {
console.warn('rez_isHidden was not 0 or 1, this should never happen, if it did, objc should error and crash the browser');
}
// [pool release]
objc_msgSend(pool, release);
objc.close();
Credit to #arai for teaching me objc jsctypes! When no one else would the guy spoon fed me!!! More people should spoon feed each other!

Get currently selected text in active application in Cocoa

I have a status-menu app that can be started using a system wide shortcut. When the app gets active, it would be great if I could somehow get the text that is selected in the currently running application.
So for example I type something in my text-editor, select the text, hit my global shortcut, my app comes up and I would now love to know the selected text from the text-editor.
What I have so far is the following (adopted code from How to get global screen coordinates of currently selected text via Accessibility APIs.)
AXUIElementRef systemWideElement = AXUIElementCreateSystemWide();
AXUIElementRef focussedElement = NULL;
AXError error = AXUIElementCopyAttributeValue(systemWideElement, kAXFocusedUIElementAttribute, (CFTypeRef *)&focussedElement);
if (error != kAXErrorSuccess) {
NSLog(#"Could not get focussed element");
} else {
AXValueRef selectedTextValue = NULL;
AXError getSelectedTextError = AXUIElementCopyAttributeValue(focussedElement, kAXSelectedTextAttribute, (CFTypeRef *)&selectedTextValue);
if (getSelectedTextError == kAXErrorSuccess) {
selectedText = (__bridge NSString *)(selectedTextValue);
NSLog(#"%#", selectedText);
} else {
NSLog(#"Could not get selected text");
}
}
if (focussedElement != NULL) CFRelease(focussedElement);
CFRelease(systemWideElement);
The problem here is that it does not work with apps like Safari and Mail...
Thanks
This is actually very easy, kAXSelectedTextAttribute is your friend.
extension AXUIElement {
var selectedText: String? {
rawValue(for: kAXSelectedTextAttribute) as? String
}
func rawValue(for attribute: String) -> AnyObject? {
var rawValue: AnyObject?
let error = AXUIElementCopyAttributeValue(self, attribute as CFString, &rawValue)
return error == .success ? rawValue : nil
}
}
This is not technically a solution to your exact question because the user would have to trigger this from the Services menu rather than it simply happening when they trigger your menu bar app.
You could use a System Service. You create a service for your app that sends the currently selected text to your menu bar app via a Pasteboard.

Resources