Orientation management in UINavigationController in iOS 10 - uinavigationcontroller

I have a very simple app, 3 UIViewControllers in UINavigationController:
I wish the first 2 UIViewControllers to work only in portrait orientation and the last one to work only in landscape one.
I subclassed UINavigation controller and I overrided three methods:
class NavCtrl: UINavigationController {
override var shouldAutorotate: Bool {
return topViewController?.shouldAutorotate ?? super.shouldAutorotate
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return topViewController?.supportedInterfaceOrientations ?? super.supportedInterfaceOrientations
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return topViewController?.preferredInterfaceOrientationForPresentation ?? super.preferredInterfaceOrientationForPresentation
}
}
In my first two view controllers I added:
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
In my last view I have:
override var shouldAutorotate: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscapeLeft
}
When I run the app all 3 views support only portrait orientation, my last view doesn't work in landscape.
I put breakpoints to shouldAutorotate and supportedInterfaceOrientations (UINavigationController) but there are called only once when the app loads and again when I rotate device but not when the view controller is pushed to nav controller.
I tried to add this line:
UIViewController.attemptRotationToDeviceOrientation()
to viewDidLoad to my last view controller to force orientation reload but with no luck.
Do you have any idea I could make my app to match my requirements?
Thanks

Related

how to animate state transitions in Blazor when rendering a list of object with their own lifecycle?

I know that there is a post closely related to my question (How to animate state transitions in Blazor?).
However, my problem is the following : Consider a list of toasts for instance. I want to display 5 toasts, they can be removed by clicking on them, or they remove themselves when their timers is out.
Code simplified as example.
Component Toasts
#inject ToastService ToastService
<div class="toasts">
#foreach (ToastData data in ToastService.List)
{
<Toast Data="data" />
}
</div>
#code {
protected override async Task OnInitializedAsync()
{
ToastService.OnListChange += RefreshList;
}
public void RefreshList()
{
StateHasChanged();
}
}
ToastService
public class ToastService
{
private List<ToastData > _list;
public List<ToastData > List
{
get
{
var list = _list.Take(5).ToList();
foreach (ToastData data in list)
{
data.StartCountDown();
}
return list;
}
}
public event Action OnListChange;
public ToastService()
{
_List = new List<ToastData >();
}
public async Task CreateToast(ToastData data)
{
_list.Add(data);
OnListChange?.Invoke();
}
public async Task RemoveToast(ToastData data)
{
_list.Remove(data);
OnListChange?.Invoke();
}
}
ToastData
public class ToastData
{
private ToastService _toastService;
private bool _isCountdownStarted;
private System.Timers.Timer _countdown;
public ToastData(ToastService toastService)
{
_toastService= toastService;
}
public void StartCountDown()
{
if (_isCountdownStarted)
return;
_countdown = new System.Timers.Timer(5000);
_countdown.AutoReset = false;
_countdown.Start();
_countdown.Elapsed += RemoveNotification;
_isCountdownStarted = true;
}
public void RemoveNotification()
{
_countdown.Close();
_toastService.RemoveNotification(this);
}
private void RemoveNotification(object source, ElapsedEventArgs args)
{
RemoveNotification();
}
}
Component Toast
<div #onclick="Clicked" class="toast">
Some message on a toast
</div>
#code {
[Parameter] public ToastData Data { get; set; }
public void Clicked()
{
Data.RemoveNotification();
}
}
The above example work fine, cause there's no animation yet.
But now, I want to add an animation of the Toast component. So I modify the ToastData to first call a Hide method, this method will notify the Toast component who will add a CSS class that will animate the removal.
This works fine, until this happen :
Toast component 1 start to animate the removal
Toast component 2 start to animate the removal
Toast 1 is removed, the list is refreshed
Toast 2 is now Toast 1, the animation is gone, and suddenly it disappear
Worst even, a Toast 3 would become Toast 2 and will animate even if not intended to be removed yet.
I understand that Blazor choose to reuse HTML, that's why in the Toasts component, all Toast component will always be the same. That's why I put the logic in the ToastData.
I'm guessing I'm missing something...
Any help or insight appreciated!
When rendering components in a loop, the #key directive attribute is your friend.
It will help Blazor keep the relationship between data and a component instance.
https://learn.microsoft.com/en-us/aspnet/core/blazor/components/?view=aspnetcore-6.0#use-key-to-control-the-preservation-of-elements-and-components

How could I change the Navigastion's page arrow in Xamarin Forms?

I'm creating an app using xamarin Forms (multiplatform), I'm using a Navigation page, but I want to change the arrow ("<-") to text ("back")
Do you know how could i do it?
Thanks
(I'm going to use it in an Android App, but I'm creating the app using Xamarin forms)
You could use custom renderer to remove the navigation icon and set it with text. But, when you do that, you need to capture the click of the text and simulate the back event.
Create the interface:
public class CustomNavigationPage : NavigationPage
{
public CustomNavigationPage(Page startupPage) : base(startupPage)
{
}
}
The implementation of Android:
[assembly: ExportRenderer(typeof(CustomNavigationPage),
typeof(NavigationPageRenderer_Droid))]
namespace NavigationPageDemo.Droid
{
public class NavigationPageRenderer_Droid : NavigationPageRenderer
{
public Android.Support.V7.Widget.Toolbar toolbar;
public Activity context;
public NavigationPageRenderer_Droid(Context context) : base(context)
{
}
protected override Task<bool> OnPushAsync(Page view, bool animated)
{
var retVal = base.OnPushAsync(view, animated);
context = (Activity)Forms.Context;
toolbar = context.FindViewById<Android.Support.V7.Widget.Toolbar>(Droid.Resource.Id.toolbar);
if (toolbar != null)
{
//if (toolbar.NavigationIcon != null)
//{
//toolbar.NavigationIcon = Android.Support.V7.Content.Res.AppCompatResources.GetDrawable(context, Resource.Drawable.back);
//toolbar.NavigationIcon = null;
toolbar.NavigationIcon = null;
toolbar.Title = "back";
toolbar.SetOnClickListener(new OnClick());
//}
}
return retVal;
}
protected override Task<bool> OnPopViewAsync(Page page, bool animated)
{
return base.OnPopViewAsync(page, animated);
}
}
public class OnClick : Java.Lang.Object, IOnClickListener
{
void IOnClickListener.OnClick(Android.Views.View v)
{
App.Current.MainPage.Navigation.PopAsync();
}
}
In the custom renderer, use the OnClickListener to capture the click on text.
when you are working with xamarin forms it is suggested make use of common components and make least use of custom renderer.
Now for your requirement you want to create custom navigation bar
so here is how you can do it.
Create BaseContent Page
Create a Control Template inside your base page your can follow this link
Inside your control template using a grid view place your label with text binding (Back),also your can place a label in center to show title of page again u can make use of template binding which u would come to know when u go through the link
Now inherit your main page with your basecontentpage page
add your control template inside your main page
turn off your navigation bar of your main page
and you are done, this would give u more power to add more things like image or toolbar in your navbar
also to dynamically handle your back button u can check the count from navigationstack if its 0 u can show Humburger Icon or if its more than 0 u can show your label using IsVisible True/False

iOS 6 UITabBarController supported orientation with current UINavigation controller

I have an iPhone app I am updating to iOS 6 that is having rotation issues. I have a UITabBarController with 16 UINavigationCotrollers. Most of the subviews can work in portrait or landscape but some of them are portrait only. With iOS 6 things are rotating when they shouldn't.
I tried subclassing the tabBarController to return the supportedInterfaceOrienations of the current navigationController's selected viewController:
- (NSUInteger)supportedInterfaceOrientations{
UINavigationController *navController = (UINavigationController *)self.selectedViewController;
return [navController.visibleViewController supportedInterfaceOrientations];
}
This got me closer. The view controller won't rotate out of position when visible, but if I am in landscape and switch tabs the new tab will be in landscape even if it isn't supported.
Ideally the app will only be in the supported orienation of the current visible view controller. Any ideas?
Subclass your UITabBarController overriding these methods:
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// You do not need this method if you are not supporting earlier iOS Versions
return [self.selectedViewController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
-(NSUInteger)supportedInterfaceOrientations
{
return [self.selectedViewController supportedInterfaceOrientations];
}
-(BOOL)shouldAutorotate
{
return YES;
}
Subclass your UINavigationController overriding these methods:
-(NSUInteger)supportedInterfaceOrientations
{
return [self.topViewController supportedInterfaceOrientations];
}
-(BOOL)shouldAutorotate
{
return YES;
}
Then implement these methods in your viewControllers that you do not want to rotate:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
-(BOOL)shouldAutorotate
{
return NO;
}
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
And for viewControllers that you do want to rotate:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
-(BOOL)shouldAutorotate
{
return YES;
}
Your tabbarController should be added as the RootviewController of the app window. If you plan to support the default orientations, all but upsidedown is default for iPhone, then you do not need to do anything else. If you want to support upside-down or if you do not want to support another of the orientations, then you need to set the appropriate values in app delegate and/or info.plist.
I had issue that some View controllers in the navigation stack support all the orientations, some only portrait, but UINavigationController was returning all app supported orientations, this little hack helped me. I'm not sure if this is intended behavior or what
#implementation UINavigationController (iOS6OrientationFix)
-(NSUInteger) supportedInterfaceOrientations {
return [self.topViewController supportedInterfaceOrientations];
}
#end
I think is better something like that (as a category method)
-(NSUInteger) supportedInterfaceOrientations {
if([self.topViewController respondsToSelector:#selector(supportedInterfaceOrientations)])
{
return [self.topViewController supportedInterfaceOrientations];
}
return UIInterfaceOrientationMaskPortrait;
}
this ensures that the method is implemented. If you aren't doing this check and the method is not implemented (like in iOS5 env) the app should crash!
If you plan to enable or disable rotation for all view controllers you don't need to subclass UINavigationController.
Instead use:
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
in your AppDelegate.
If you plan to support all orientations in your app but different orientations on PARENT View Controllers (UINavigationController stack for example) you should use
-(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
from AppDelegate in combination with the following methods in your PARENT View Controller.
- (BOOL)shouldAutorotate
and
- (NSUInteger)supportedInterfaceOrientations
But if you plan to have different orientation settings in different CHILDREN ViewControllers in the same navigation stack (like me) you need to check the current ViewController in the navigation stack.
I've created the following in my UINavigationController subclass:
- (BOOL)shouldAutorotate
{
return YES;
}
- (NSUInteger)supportedInterfaceOrientations
{
int interfaceOrientation = 0;
if (self.viewControllers.count > 0)
{
DLog(#"%#", self.viewControllers);
for (id viewController in self.viewControllers)
{
if ([viewController isKindOfClass:([InitialUseViewController class])])
{
interfaceOrientation = UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
}
else if ([viewController isKindOfClass:([MainViewController class])])
{
interfaceOrientation = UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
}
else
{
interfaceOrientation = UIInterfaceOrientationMaskAllButUpsideDown;
}
}
}
return interfaceOrientation;
}
Since you cannot control anymore from children ViewControllers the rotation settings of presented view controller you must somehow intercept what view controller is currently in the navigation stack. So that's what I did :). Hope that helps !

Structuring a MonoTouch.Dialog application

From the examples at Xamarin.com you can build basic M.T. Dialog apps, but how do you build a real life application?
Do you:
1) Create a single DialogViewController and tree every view/RootElement from there or,
2) Create a DialogViewController for every view and use the UINavigationController and push it on as needed?
Depending on your answer, the better response is how? I've built the example task app, so I understand adding elements to a table, click it to go to the 'next' view for editing, but how to click for non-editing? How to click a button, go next view if answer is number 1?
Revised:
There is probably no one right answer, but what I've come up with seems to work for us. Number 2 from above is what was chosen, below is an example of the code as it currently exists. What we did was create a navigation controller in AppDelegate and give access to it throughout the whole application like this:
public partial class AppDelegate : UIApplicationDelegate
{
public UIWindow window { get; private set; }
//< There's a Window property/field which we chose not to bother with
public static AppDelegate Current { get; private set; }
public UINavigationController NavController { get; private set; }
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
Current = this;
window = new UIWindow (UIScreen.MainScreen.Bounds);
NavController = new UINavigationController();
// See About Controller below
DialogViewController about = new AboutController();
NavController.PushViewController(about, true);
window.RootViewController = NavController;
window.MakeKeyAndVisible ();
return true;
}
}
Then every Dialog has a structure like this:
public class AboutController : DialogViewController
{
public delegate void D(AboutController dvc);
public event D ViewLoaded = delegate { };
static About about;
public AboutController()
: base(about = new About())
{
Autorotate = true;
about.SetDialogViewController(this);
}
public override void LoadView()
{
base.LoadView();
ViewLoaded(this);
}
}
public class About : RootElement
{
static AboutModel about = AboutVM.About;
public About()
: base(about.Title)
{
string[] message = about.Text.Split(...);
Add(new Section(){
new AboutMessage(message[0]),
new About_Image(about),
new AboutMessage(message[1]),
});
}
internal void SetDialogViewController(AboutController dvc)
{
var next = new UIBarButtonItem(UIBarButtonSystemItem.Play);
dvc.NavigationItem.RightBarButtonItem = next;
dvc.ViewLoaded += new AboutController.D(dvc_ViewLoaded);
next.Clicked += new System.EventHandler(next_Clicked);
}
void next_Clicked(object sender, System.EventArgs e)
{
// Load next controller
AppDelegate.Current.NavController.PushViewController(new IssuesController(), true);
}
void dvc_ViewLoaded(AboutController dvc)
{
// Swipe location: https://gist.github.com/2884348
dvc.View.Swipe(UISwipeGestureRecognizerDirection.Left).Event +=
delegate { next_Clicked(null, null); };
}
}
Create a sub-class of elements as needed:
public class About_Image : Element, IElementSizing
{
static NSString skey = new NSString("About_Image");
AboutModel about;
UIImage image;
public About_Image(AboutModel about)
: base(string.Empty)
{
this.about = about;
FileInfo imageFile = App.LibraryFile(about.Image ?? "filler.png");
if (imageFile.Exists)
{
float size = 240;
image = UIImage.FromFile(imageFile.FullName);
var resizer = new ImageResizer(image);
resizer.Resize(size, size);
image = resizer.ModifiedImage;
}
}
public override UITableViewCell GetCell(UITableView tv)
{
var cell = tv.DequeueReusableCell(skey);
if (cell == null)
{
cell = new UITableViewCell(UITableViewCellStyle.Default, skey)
{
SelectionStyle = UITableViewCellSelectionStyle.None,
Accessory = UITableViewCellAccessory.None,
};
}
if (null != image)
{
cell.ImageView.ContentMode = UIViewContentMode.Center;
cell.ImageView.Image = image;
}
return cell;
}
public float GetHeight(UITableView tableView, NSIndexPath indexPath)
{
float height = 100;
if (null != image)
height = image.Size.Height;
return height;
}
public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath indexPath)
{
//base.Selected(dvc, tableView, path);
tableView.DeselectRow(indexPath, true);
}
}
#miquel
The current idea of a workflow is an app that starts with a jpg of the Default.png that fades into the first view, with a flow control button(s) that would move to the main app. This view, which I had working previous to M.T.D. (MonoTouch.Dialog), which is a table of text rows with an image. When each row is clicked, it moves to another view that has the row/text in more detail.
The app also supports in-app-purchasing, so if the client wishes to purchase more of the product, then switch to another view to transact the purchase(s). This part was the main reason for switching to M.T.D., as I thought M.T.D. would be perfect for it.
Lastly there would be a settings view to re-enable purchases, etc.
PS How does one know when the app is un-minimized? We would like to show the fade in image again.
I have been asking myself the same questions. I've used the Funq Dependency Injection framework and I create a new DialogViewController for each view. It's effectively the same approach I've used previously developing ASP.NET MVC applications and means I can keep the controller logic nicely separated. I subclass DialogViewController for each view which allows me to pass in to the controller any application data required for that particular controller. I'm not sure if this is the recommended approach but so far it's working for me.
I too have looked at the TweetStation application and I find it a useful reference but the associated documentation specifically says that it isn't trying to be an example of how to structure a MonoTouch application.
I use option 2 that you stated as well, it works pretty nicely as you're able to edit the toolbar options on a per-root-view basis and such.
Option 2 is more feasible, as it also gives you more control on each DialogViewController. It can also helps if you want to conditionally load the view.

Defining a custom UINavigationBar through subclassing removes navigation items

I'm trying to follow the standard approach to creating a custom UINavigationBar in order to change its background image, but have found an issue in the subclassing process. If I subclass UINavigationController, with the intent of overriding the virtual NavigationBar property to provide my own implementation, all navigation items (any left or right buttons, and the title view) disappear. At first I thought it was due to the background being rendered over top of the navigation items, but I can reproduce the problem with a no-op subclass.
It's reproducible with the following code:
[Register("NavigationBar")]
public class NavigationBar : UINavigationBar
{
public NavigationBar () : base()
{
}
public NavigationBar (NSCoder coder) : base(coder)
{
}
public NavigationBar (IntPtr ptr) : base(ptr)
{
}
public NavigationBar (NSObjectFlag t) : base(t)
{
}
public NavigationBar (RectangleF frame) : base(frame)
{
}
}
[Register("NavigationController")]
public class NavigationController : UINavigationController
{
private UINavigationBar _navBar;
public NavigationController () : base()
{
}
public NavigationController (NSCoder coder) : base(coder)
{
}
public NavigationController (IntPtr ptr) : base(ptr)
{
}
public NavigationController (NSObjectFlag t) : base(t)
{
}
public override UINavigationBar NavigationBar
{
get
{
if(_navBar == null)
{
return base.NavigationBar;
}
return _navBar;
}
}
public void SetNavigationBar(UINavigationBar navigationBar)
{
_navBar = (UINavigationBar)navigationBar;
}
}
Now, all you need to do to lose your navigation items is to use the custom classes instead of the default ones:
var navigationBar = new NavigationBar();
navigationBar.BarStyle = UIBarStyle.Black;
navigationBar.TintColor = HeaderColor;
var navigationController = new NavigationController();
navigationController.SetNavigationBar(navigationBar);
// ...
Well, your SetNavigationBar() method doesn't pass that down to the native base class and since you don't do any explicit drawing yourself, how is the native drawing code ever supposed to be invoked for your custom NavigationBar class?
In your example code, that NavigationBar is just floating around in space and never gets told to draw.
In order to subclass UINavigationBar, you must define the IntPtr constructor in your derived class and instantiate the UINavigationController using the public UINavigationController(Type navigationBarType, Type toolbarType) constructor. Example:
public class MyNavigationBar: UINavigationBar
{
public MyNavigationBar(IntPtr h) : base(h)
{
}
// Do something.
}
....
var navController = new UINavigationController(typeof(MyNavigationBar), typeof(UIToolbar));
Took me a while to figure it out. More information on this page: http://developer.xamarin.com/guides/ios/platform_features/introduction_to_ios_6/ in section Subclassing UINavigationBar.
Can you create a sample project where you're adding NavigationItems directly to a UINavigationController and then using the sub-classed UINavigationController/UINavigationBar causes these buttons to disappear?
Thanks,
ChrisNTR
After a lot of research and back and forth with Xamarin, the answer to this problem is that you must use an IB stub file that is essentially no-op, but exists to shuttle the desired base type for your navigation elements. There is a working example on my OSS project: http://github.com/danielcrenna/artapp

Resources