I've set up a static UITableView with iOS Designer (IB in Objective-C world). But the orientation is changed despite I want to restrict it.
I've done the following:
In Properties under Simulated Metrics I chose Portrait as Orientation. Than I'm implementing the following functions for my UITableViewController:
public override bool ShouldAutorotate ()
{
return false;
}
public override UIInterfaceOrientationMask GetSupportedInterfaceOrientations ()
{
return UIInterfaceOrientationMask.Portrait;
}
public override UIInterfaceOrientation PreferredInterfaceOrientationForPresentation ()
{
return UIInterfaceOrientation.Portrait;
}
GetSupportedInterfaceOrientations is called and I return Portrait but the view is still rotated. What I'm missing?
Edit:
I've used the approach discussed in View Orientation. This works for my view controllers. The static UITableViewController is pushed in this way on the stack:
this.PresentViewController (new UINavigationController(myStaticTableViewController), true, null);
Here the standard implementation of UINavigationController is used. I also tried it with my CustomNavigationController which implements
partial class CustomNavigationController : UINavigationController
{
public CustomNavigationController (IntPtr handle) : base (handle)
{
}
public override bool ShouldAutorotate ()
{
return TopViewController.ShouldAutorotate();
}
public override UIInterfaceOrientation PreferredInterfaceOrientationForPresentation ()
{
return TopViewController.PreferredInterfaceOrientationForPresentation ();
}
}
but I can't do something like this
this.PresentViewController (new CustomNavigationController(myStaticTableViewController), true, null);
because it cannot convert my table view controller to IntPtr. Perhaps that's the reason why it doesn't respect the interface orientation. What solutions do I have?
Seems that I only had to add another constructor as stated in the linked thread. Now my CustoMNavigationController looks like this:
partial class CustomNavigationController : UINavigationController
{
public CustomNavigationController(UIViewController rootViewController) : base(rootViewController)
{
}
public CustomNavigationController (IntPtr handle) : base (handle)
{
}
public override bool ShouldAutorotate ()
{
return TopViewController.ShouldAutorotate();
}
public override UIInterfaceOrientation PreferredInterfaceOrientationForPresentation ()
{
return TopViewController.PreferredInterfaceOrientationForPresentation ();
}
}
Now I can use
this.PresentViewController (new CustomNavigationController(myStaticTableViewController), true, null)
and everything works as expected.
Related
I am trying to implement a custom ImageRenderer in iOS subclassing the native UIImageView, but I am having some problems with CreateNativeControl.
In the older Xamarin.Forms version (like 4.2) the custom native class that I initialized with protected override UIImageView CreateNativeControl() { return new NativeImage(); } looks like it never get called (the message I log in the constructor is not shown). The Custom Renderer is correctly initialized (the right message is logged).
In the latest stable version (like 4.4) in overriding of CreateNativeControl the return type it is said that has to be a FormsUIImageView, never heard of it, anyway I also tried to subclass that but same problem as before, it seems it never get called as the constructor message is not logged. The Custom Renderer is correctly initialized (the right message is logged).
Here the code I used:
public class IOSImageView : ImageRenderer
{
public IOSImageView()
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if(Control == null)
{
return;
}
Console.WriteLine("PIPPO created from Custom Renderer"); //this message is correctly logged
}
protected override UIImageView CreateNativeControl() //FormsUIImageView in XF 4.4
{
return new NativeImage();
}
}
public class NativeImage : UIImageView //FormsUIImageView in XF 4.4
{
public NativeImage() : base()
{
Console.WriteLine("PIPPO created from native IOS"); //this message is NOT logged
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
Console.WriteLine("PIPPO touched"); //this (of course because no NativeImage is shown and there is no image to touch) is NOT logged
}
}
FormsUIImageView is new after XF 4.4 which you can check Xamarin.Forms release notes
In your case , you seems want to set the Image Renderer as your custom ImageView, right?
You should invoked SetNativeControl()
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if(Control!=null)
{
SetNativeControl(new NativeImage());
}
}
public class NativeImage : FormsUIImageView
{
public NativeImage() : base()
{
this.UserInteractionEnabled = true;
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
Console.WriteLine("PIPPO touched"); //this (of course because no NativeImage is shown and there is no image to touch) is NOT logged
}
}
public void addStatement(UIButton button , UIViewController/UIView view){
//view.add(otherView);
}
this work with UIViewController but i want to use with UIView as well, how i do for support (UIViewController and UIView) the two ?
You can overload the addStatement method like:
public void addStatement(UIButton button, UIViewController view)
{
view.PresentViewController(navController, true, null);
}
public void addStatement(UIButton button, UIView view)
{
view.add(otherView);
}
The compiler will find the correct one, depending on the passed values.
If you have shared functionality, you can do it similar, but call the overloaded methods from the other ones, like:
public void addStatement(UIButton button, UIViewController view)
{
addStatement(button, view.View)
}
public void addStatement(UIButton button, UIView view)
{
view.add(otherView);
}
You can keep it all in one method
public void addStatement(UIButton button, UIViewController currentView = null, UIView otherview = null)
{
if (otherview != null)
{
// do something with otherview
//return
}
if(currentView != null)
{
// do something with currentView
//return
}
}
I am trying to prevent the user of my app from pressing the hardware back button.
I have found this code snippet that is in the code behind the xaml file:
protected override bool OnBackButtonPressed()
{
return true;
}
I have tried variations of this including using Boolean instead of Bool and returning base.functionname nothing seems to fire this method.
Here is the bigger context:
Code behind:
namespace Watson.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class StartScan : ContentPage
{
public StartScan()
{
InitializeComponent();
}
protected override bool OnBackButtonPressed()
{
return true;
}
}
}
This is the second page in the stack and disabling the button only needs to happen on this page only, no where else.
Any help would be appreciated.
This is working for me, I tried in Android and iOS platforms using Xamarin.Forms.
Hope you can resolve with this piece of code.
namespace Test
{
public partial class TestPage2 : ContentPage
{
public TestPage2()
{
NavigationPage.SetHasBackButton(this, false);
InitializeComponent();
}
protected override bool OnBackButtonPressed()
{
//return base.OnBackButtonPressed();
return true;
}
}
}
Thanks,
You can also add NavigationPage.SetHasBackButton property in the XAML
NavigationPage.HasBackButton="True"
In the Content Page
You can do this way:
namespace Watson.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class StartScan : ContentPage
{
public StartScan()
{
NavigationPage.SetHasBackButton(this, false);
InitializeComponent();
}
protected override bool OnBackButtonPressed()
{
// you can put some actions here (for example, an dialog prompt, ...)
return true;
}
}
}
For me it is working with the following code (Samsung Android)
protected override bool OnBackButtonPressed()
{
//base.OnBackButtonPressed();
browser.GoBack();
return true;
}
OnBackButtonPressed firing when you click the back button on your device, not from your navigation page, do your logic in OnDisappearing!
Is it possible to hide a status bar in a PageRenderer?
I tried setting these in Info.plist
<key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
And then overriding PrefersStatusBarHidden in the PageRenderer as
public override bool PrefersStatusBarHidden()
{
return true;
}
If I set UIViewControllerBasedStatusBarAppearance in Info.plist to false, it is hidden on all pages.
How can I fix this?
You should use a custom UINavigationController instead of the system one, like this sample:
In AppDelegate.cs:
UINavigationController navController;
UIWindow window;
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
{
navController = new UINavigationController (new TestViewCtonroller ());
// create a new window instance based on the screen size
window = new UIWindow (UIScreen.MainScreen.Bounds);
window.RootViewController = navController;
window.MakeKeyAndVisible ();
return true;
}
MyNavigationController.cs:
public class MyNavigationController : UINavigationController
{
public MyNavigationController (UIViewController rootController) : base(rootController)
{
}
public override UIViewController ChildViewControllerForStatusBarHidden ()
{
return TopViewController;
}
public override UIViewController ChildViewControllerForStatusBarStyle ()
{
return TopViewController;
}
}
TestViewCtonroller.cs:
public class TestViewCtonroller : UIViewController
{
public TestViewCtonroller ()
{
this.View.BackgroundColor = UIColor.Red;
}
public override UIStatusBarStyle PreferredStatusBarStyle ()
{
return UIStatusBarStyle.LightContent;
}
public override bool PrefersStatusBarHidden ()
{
return true;
}
}
Hope it can help you.
I have a classic master-detail logic and trying to create instance of detail UIViewController with InstantiateViewController inside a click event.
Here is my code,
MasterViewController.cs
detailButton += delegate {
UIStoryboard Storyboard = UIStoryboard.FromName ("Main", null);
Console.WriteLine("InstantiateViewController() started");
var profileController = Storyboard.InstantiateViewController("ProfileController") as ProfileController;
Console.WriteLine("InstantiateViewController() ended");
profileController.Id = 5;
};
ProfileViewController.cs
public partial class ProfileController : UIViewController
{
public int Id { get; set; }
public override void ViewDidLoad()
{
Console.WriteLine("ViewDidLoad() called");
}
}
When I click the button output is,
InstantiateViewController() started
ViewDidLoad() called
InstantiateViewController() ended
This means profileController.Id is set after ViewDidLoad() which means I can't load data by Id in ViewDidload event beacuse Id is null.
So my question is why ViewDidLoad() called by InstantiateViewController(), in which method should I load data by Id?
Thanks.
VoewDidLoad is called when the ViewController is loaded into memory.
So, the correct place to get the data is on ViewDidAppear.
ViewDidAppear notifies the ViewController that its view was added to a view hierarchy.
UPDATE:
Based on the new information provided in comments you could do something like this:
public partial class ProfileController : UIViewController
{
private int _id;
public void SetupProfile (int id)
{
// Save the Id if necessary.
_id = id;
// Add event with this id related.
}
public override void ViewDidLoad()
{
Console.WriteLine("ViewDidLoad() called");
}
}
In alternative if you still want to do the event setup in ViewDidAppear you could use this approach with the events:
yourClass.Event -= MyHandler;
yourClass.Event += MyHandler;
I would do this via a custom segue.
1) Create a custom segue that can be re-used throughout your app:
public class CustomSeque : UIStoryboardSegue // or UIStoryboardPopoverSegue depending upon UI design so you can "POP" controller
{
public CustomSeque(String identifier, UIViewController source, UIViewController destination) : base (identifier, source, destination) { }
public override void Perform()
{
if (Identifier == "StackOverflow")
{
// Are you using a NavigationController?
if (SourceViewController.NavigationController != null)
SourceViewController.NavigationController?.PushViewController(DestinationViewController, animated: true);
else
SourceViewController.ShowViewController(DestinationViewController, this);
} else
base.Perform();
}
}
2) Then you can:
UIStoryboard Storyboard = UIStoryboard.FromName("Main", null);
Console.WriteLine("InstantiateViewController() started");
var profileController = Storyboard.InstantiateViewController("ProfileController") as ProfileController;
var seque = new CustomSeque($"StackOverflow", this, profileController);
profileController.Id = 5;
profileController.PrepareForSegue(seque, this); // instead of *this*, you can pass any NSObject that contains data for your controller
seque.Perform();
Console.WriteLine("InstantiateViewController() ended");
If your ProfileController looks like this:
public partial class ProfileController : UIViewController
{
public ProfileController (IntPtr handle) : base (handle)
{
Id = -99;
}
public int Id { get; set; }
public override bool ShouldPerformSegue(string segueIdentifier, NSObject sender)
{
if (segueIdentifier == "StackOverflow")
return true;
return base.ShouldPerformSegue(segueIdentifier, sender);
}
[Export("prepareForSegue:sender:")]
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
{
base.PrepareForSegue(segue, sender);
Console.WriteLine("ProfileController.PrepareForSegue()");
Console.WriteLine($" - ID = {Id}");
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
Console.WriteLine("ProfileController.ViewDidLoad()");
Console.WriteLine($" - ID = {Id}");
}
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
Console.WriteLine("ProfileController.ViewWillAppear()");
Console.WriteLine($" - ID = {Id}");
}
}
Your sequenced output would be:
InstantiateViewController() started
ProfileController.PrepareForSegue()
- ID = 5
ProfileController.ViewDidLoad()
- ID = 5
InstantiateViewController() ended
ProfileController.ViewWillAppear()
- ID = 5