I'd like to have a UIPickerView in a Xamarin.iOS project. The UIPicker that I need, must be like this (hide by default and with toolbar and done button):
which is an example for Xamarin.forms !
I've seen already all questions on stack overflow and they are not in my case or they are not complete explanation for this purpose.
For demonstrating that I've tried already for create Done Toolbar, here is my code :
public class TestPickerViewController : UIViewController
{
PickerModel picker_model;
UIPickerView picker;
public TestPickerViewController()
{
Title = Texts.Home;
View.BackgroundColor = UIColor.White;
this.EdgesForExtendedLayout = UIRectEdge.None;
}
public override void DidReceiveMemoryWarning()
{
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
List<Object> state_list = new List<Object>();
state_list.Add("1");
state_list.Add("2");
state_list.Add("3");
state_list.Add("4");
picker_model = new PickerModel(state_list);
picker = new UIPickerView();
picker.Model = picker_model;
picker.ShowSelectionIndicator = true;
UIToolbar toolbar = new UIToolbar();
toolbar.BarStyle = UIBarStyle.Black;
toolbar.Translucent = true;
toolbar.SizeToFit();
UIBarButtonItem doneButton = new UIBarButtonItem("Done", UIBarButtonItemStyle.Done, (s, e) =>
{
foreach (UIView view in this.View.Subviews)
{
if (view.IsFirstResponder)
{
UITextField textview = (UITextField)view;
textview.Text = picker_model.values[(int)picker.SelectedRowInComponent(0)].ToString();
textview.ResignFirstResponder();
}
}
});
toolbar.SetItems(new UIBarButtonItem[] { doneButton }, true);
View.AddSubviews(picker);
//How to add toolbar, action for opening toolbar and hide by default the list
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
View.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
View.AddConstraints(
picker.AtTopOf(View, 90),
picker.AtLeftOf(View, 50),
picker.WithSameWidth(View).Minus(100)
);
}
private void SetPicker(object sender, EventArgs e)
{
UITextField field = (UITextField)sender;
picker.Select(picker_model.values.IndexOf(field.Text), 0, true);
}
}
public class PickerModel : UIPickerViewModel
{
public IList<Object> values;
public event EventHandler<PickerChangedEventArgs> PickerChanged;
public PickerModel(IList<Object> values)
{
this.values = values;
}
public override nint GetComponentCount(UIPickerView picker)
{
return 1;
}
public override nint GetRowsInComponent(UIPickerView picker, nint component)
{
return values.Count;
}
public override string GetTitle(UIPickerView picker, nint row, nint component)
{
return values[(int)row].ToString();
}
public override nfloat GetRowHeight(UIPickerView picker, nint component)
{
return 40f;
}
public override void Selected(UIPickerView picker, nint row, nint component)
{
if (this.PickerChanged != null)
{
this.PickerChanged(this, new PickerChangedEventArgs { SelectedValue = values[(int)row] });
}
}
}
public class PickerChangedEventArgs : EventArgs
{
public object SelectedValue { get; set; }
}
I know that I have to add toolbar to somewhere which has done button. And I need also the action which hide the default Picker and show the list when we click on Select section and etc ...
Just assign the UIToolbar to the InputAccessoryView property of UITextField. Here's a code snippet for example:
UIToolbar toolBar = new UIToolbar(new CGRect(0, 0, 320, 44));
UIBarButtonItem flexibleSpaceLeft = new UIBarButtonItem(UIBarButtonSystemItem.FlexibleSpace,null,null);
UIBarButtonItem doneButton = new UIBarButtonItem("OK",UIBarButtonItemStyle.Done,this, new ObjCRuntime.Selector("DoneAction"));
UIBarButtonItem[] list = new UIBarButtonItem[] { flexibleSpaceLeft, doneButton };
toolBar.SetItems(list, false);
UIPickerView pickerView = new UIPickerView(new CGRect(0, 44, 320, 216));
pickerView.DataSource = new MyUIPickerViewDataSource();
pickerView.Delegate = new MyUIPickerViewDelegate();
pickerView.ShowSelectionIndicator = true;
//Assign the toolBar to InputAccessoryView
textField.InputAccessoryView = toolBar;
textField.InputView = pickerView;
And implement the Action like this:
[Export("DoneAction")]
private void DoneAction()
{
Console.WriteLine("Your Action!");
}
It works like this:
Related
I have form in xamarin.forms and I want to show a popup message when users click on the nav bar button if there are pending data to save. I found this example but it doesn't not working on Xamarin.Forms 5.0
Any idea of how to do it?
I did a quick test on this you can refer to it.
First, I create a contentpage and set CustomBackButtonAction, EnableBackButtonOverride to add navigate method:
public partial class TestPage5 : ContentPage
{public Action CustomBackButtonAction { get; set; }
public static readonly BindableProperty EnableBackButtonOverrideProperty = BindableProperty.Create(
nameof(EnableBackButtonOverride),
typeof(bool),
typeof(TestPage5),
false
);
public bool EnableBackButtonOverride {
get { return (bool)GetValue(EnableBackButtonOverrideProperty); }
set { SetValue(EnableBackButtonOverrideProperty, value); }
}
public TestPage5()
{
InitializeComponent();
EnableBackButtonOverride = true;
CustomBackButtonAction = async () => { var result = await DisplayAlert("Alert", "Are you Sure?", "Yes", "No");
if (result)
{ await Navigation.PopAsync(true); } };
}
}
Then create renderer on ios while override OnOptionsItemSelected on android:
ios(create a new backbutton and override):
[assembly:ExportRenderer(typeof(TestPage5),typeof(MyRenderer))]
namespace My_Forms_Test3.iOS
{
public class MyRenderer:Xamarin.Forms.Platform.iOS.PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (((TestPage5)Element).EnableBackButtonOverride)
{
SetButton();
}
}
private void SetButton()
{
var backbuttonimg = UIImage.FromBundle("backarrow.png");
backbuttonimg = backbuttonimg.ImageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate);
var backbutton = new UIButton(UIButtonType.Custom)
{ HorizontalAlignment=UIControlContentHorizontalAlignment.Left,
TitleEdgeInsets=new UIEdgeInsets(11.5f,15f,10f,0f),
ImageEdgeInsets=new UIEdgeInsets(1f,8f,0f,0f)};
backbutton.SetTitle("Back", UIControlState.Normal);
backbutton.SetTitleColor(UIColor.White, UIControlState.Normal);
backbutton.SetTitleColor(UIColor.LightGray, UIControlState.Highlighted);
backbutton.Font = UIFont.FromName("HelveticaNeue", (nfloat)17);
backbutton.SetImage(backbuttonimg, UIControlState.Normal);
backbutton.SizeToFit();
backbutton.TouchDown += (sender, e) =>
{
if (((TestPage5)Element)?.CustomBackButtonAction != null)
{
((TestPage5)Element)?.CustomBackButtonAction.Invoke();
}
};
backbutton.Frame = new CoreGraphics.CGRect(0, 0, UIScreen.MainScreen.Bounds.Width / 4,
NavigationController.NavigationBar.Frame.Height);
var buttoncontainer = new UIView(new CoreGraphics.CGRect(0, 0, backbutton.Frame.Width, backbutton.Frame.Height));
buttoncontainer.AddSubview(backbutton);
var fixspace = new UIBarButtonItem(UIBarButtonSystemItem.FixedSpace)
{ Width = -16f };
var backbuttonitem = new UIBarButtonItem("", UIBarButtonItemStyle.Plain, null) { CustomView = backbutton };
NavigationController.TopViewController.NavigationItem.LeftBarButtonItems = new[] { fixspace, backbuttonitem };
}
}
}
android:
add following on main activity:
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
//important to trigger OnOptionItemSelected
Android.Support.V7.Widget.Toolbar toolbar
= this.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
also in mainactivity.cs:
public override bool OnOptionsItemSelected(IMenuItem item)
{
// check if the current item id
// is equals to the back button id
if (item.ItemId == 16908332) // xam forms nav bar back button id
{
// retrieve the current xamarin
// forms page instance
var currentpage = (TestPage5)Xamarin.Forms.Application.Current.
MainPage.Navigation.NavigationStack.LastOrDefault();
// check if the page has subscribed to the custom back button event
if (currentpage?.CustomBackButtonAction != null)
{
// invoke the Custom back button action
currentpage?.CustomBackButtonAction.Invoke();
// and disable the default back button action
return false;
}
// if its not subscribed then go ahead
// with the default back button action
return base.OnOptionsItemSelected(item);
}
else
{
// since its not the back button
//click, pass the event to the base
return base.OnOptionsItemSelected(item);
}
}
//android Hardware back button event
public override void OnBackPressed()
{
// this is really not necessary, but in Android user has both Nav bar back button
// and physical back button, so its safe to cover the both events
var currentpage = (BaseContentPage)Xamarin.Forms.Application.Current.
MainPage.Navigation.NavigationStack.LastOrDefault();
if (currentpage?.CustomBackButtonAction != null)
{
currentpage?.CustomBackButtonAction.Invoke();
}
else
{
base.OnBackPressed();
}
}
Here is the full blog I have written which handles the same,
Android:
I have used NavigationPage Renderer to achieve this functionality in android
Android Implementtion
iOS:
I have used Page Renderer to achieve this functionality in iOS
public class CustomPageRenderer:PageRenderer
{
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
if (Element != null && Element is BasePage basePage && basePage.BindingContext != null &&
basePage.BindingContext is BaseViewModel baseViewModel)
{
SetCustomBackButton(baseViewModel);
}
}
private void SetCustomBackButton(BaseViewModel baseViewModel)
{
UIButton btn = new UIButton();
btn.Frame = new CGRect(0, 0, 50, 40);
btn.BackgroundColor = UIColor.Clear;
btn.TouchDown += (sender, e) =>
{
// Whatever your custom back button click handling
baseViewModel.BackPressedAction?.Invoke(false);
};
//var views = NavigationController?.NavigationBar.Subviews;
NavigationController?.NavigationBar.AddSubview(btn);
}
}
Note:
Do create BackPressedAction Action in your base view model to capture the back press event
I am using the code below to make the Text in the TableSections of my CustomTableView Blue (DodgerBlue to be specific) and also make the Text Bold.
assembly: ExportRenderer(typeof(CustomTableView), typeof(CustomTableViewRenderer))]
namespace MyApp.iOS.CustomRenderers
{
public class CustomTableViewRenderer : TableViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
var tableView = Control as UITableView;
var customTableView = Element as CustomTableView;
tableView.WeakDelegate = new CustomHeaderTableModelRenderer(customTableView);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
}
private class CustomHeaderTableModelRenderer : UnEvenTableViewModelRenderer
{
private readonly CustomTableView _customTableView;
public CustomHeaderTableModelRenderer(TableView model) : base(model)
{
_customTableView = model as CustomTableView;
}
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
return new UILabel()
{
Text = TitleForHeader(tableView, section),
TextColor = UIColor.FromRGB(30, 114, 255), //Issue is here
Font = UIFont.BoldSystemFontOfSize(new nfloat(22.0)),
};
}
}
}
}
The problem I have is that instead of setting the TextColor for the text in the Header with RGB values and forcing it to always be DodgerBlue, I would like to get the color from the TextColor property of each TableSection and use that instead (this will allow me to reuse this CustomTableView elsewhere since I can change the color as I wish). Any idea on how I can do this would be really appreciated
Was able to do this using the code below:
private class CustomHeaderTableModelRenderer : UnEvenTableViewModelRenderer
{
private readonly CustomTableView _customTableView;
public CustomHeaderTableModelRenderer(TableView model) : base(model)
{
_customTableView = model as CustomTableView;
}
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
var tableSectionColor = View.Model.GetSectionTextColor((int) section); // This line gets the Color of the Text in the TitleSection.
if (tableSectionColor.IsDefault)
{
return new UILabel()
{
Text = TitleForHeader(tableView, section),
Font = UIFont.BoldSystemFontOfSize(new nfloat(22.0)),
};
}
return new UILabel()
{
Text = TitleForHeader(tableView, section),
TextColor = tableSectionColor.ToUIColor(), //Use the color here.
Font = UIFont.BoldSystemFontOfSize(new nfloat(22.0)),
};
}
}
I used sidebar navigation xamarin component in my app, but navigationbar is showing on the sidemenu, it does not slide with the navigation controller.
xamarin component: https://components.xamarin.com/view/sidebarnavigation
Here is my code.
public AgentDetails agent { get; set; }
public SidebarController SidebarController { get; private set; }
public RootController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
}
public override void LoadView()
{
base.LoadView();
UIImageView img = new UIImageView(new CGRect(0, 0, 120, 50));
img.Image = UIImage.FromBundle("Logo.png");
img.ContentMode = UIViewContentMode.ScaleAspectFit;
this.NavigationItem.LeftBarButtonItem = new UIBarButtonItem(img);
//this.NavigationItem.Title = true;
//menuOpen.SetBackgroundImage(UIImage.FromBundle("Menu.png"), UIControlState.Normal);
UIBarButtonItem back = new UIBarButtonItem();
back.SetBackButtonBackgroundImage(UIImage.FromBundle("Back.png"), UIControlState.Normal, UIBarMetrics.Compact);
this.NavigationItem.BackBarButtonItem = back;
this.NavigationController.NavigationBar.BackgroundColor = UIColor.FromRGB(255, 0, 0);
var storyboard = UIStoryboard.FromName("Main", null);
MainTabController tab = storyboard.InstantiateViewController("TabPage") as MainTabController;
SideMenuController side = storyboard.InstantiateViewController("SideMenu") as SideMenuController;
side.rootController = this;
tab.agent = agent;
SidebarController = new SidebarController(this, tab, side);
this.NavigationController.NavigationBar.Translucent = false;
menuOpen.TouchUpInside += MenuOpen_TouchUpInside;
LoadSchedule();
}
void LoadSchedule()
{
AgentAPI agentData = new AgentAPI();
agentData.EmpId = agent.PortalId;
agentData.ScheduleHours((schedule) =>
{
JObject jsondata = JsonConvert.DeserializeObject<JObject>(schedule);
if (jsondata["status"].ToString() == "200")
{
agent.Schedule = JsonConvert.DeserializeObject<List<Schedule>>(jsondata["activity"].ToString());
}
});
}
private void MenuOpen_TouchUpInside(object sender, EventArgs e)
{
SidebarController.ToggleMenu();
}
image of my sidemenu
Don't embed the RootViewController in the NavigationController.
Embed the ContentController instead.
For example , the code snippet in RootViewController.cs like this:
SidebarController = new SidebarController(this, new UINavigationController(new ContentController()), new SideMenuController());
I´m very new to Xamarin and currently I want to develop a little iOS app that receives data from a REST-Service.
So I have the following Storyboard:
In the table view there should be some entries that are consumed from the REST-Service. When you click on one row you should be "redirected" to a detail view which should be the right View.
The problem is, that the screen of the right story board is just black:
Well let´s have a look at my code, first the FinishedLaunching method in my AppDelegate.cs:
public override void FinishedLaunching(UIApplication application)
{
Window = new UIWindow(UIScreen.MainScreen.Bounds);
UIStoryboard storyboard;
UIViewController initialViewController;
var credential = CredentialController.Instance.FindAccount(CustomStaticLiterals.AppName);
if (credential == null)
{
storyboard = UIStoryboard.FromName("AccountCreationViewController", NSBundle.MainBundle);
initialViewController = storyboard.InstantiateInitialViewController() as UIViewController;
}
else
{
storyboard = UIStoryboard.FromName("OverviewViewController", NSBundle.MainBundle);
initialViewController = storyboard.InstantiateInitialViewController() as UIViewController;
}
Window.RootViewController = initialViewController;
Window.MakeKeyAndVisible();
}
Here is my ProjectOverviewController:
public partial class ProjectOverviewController : UITableViewController
{
public List<Project> Projects { get; set; }
private static readonly NSString callHistoryCellId = new NSString("ProjectCell");
public ProjectOverviewController(IntPtr handle) : base(handle)
{
TableView.RegisterClassForCellReuse(typeof(UITableViewCell), callHistoryCellId);
ProjectOverviewDataSource source = new ProjectOverviewDataSource(this);
source.DetailProjectEvent += DetailProjectPage;
TableView.Source = source;
Projects = new List<Project>();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var credentials = CredentialController.Instance.FindAccount(CustomStaticLiterals.AppName);
var service = new ServiceInvoker();
Projects = new List<Project>(service.GetCurrentProjectsOfUserAsync(credentials.UserName).Result);
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
}
void DetailProjectPage(object sender, EventArgs e, int index)
{
var currentProject = Projects[index];
var projectViewController =
Storyboard.InstantiateViewController("ProjectViewController") as ProjectViewController;
if (projectViewController != null)
{
projectViewController.Initialize(currentProject);
NavigationController.PushViewController(projectViewController, true);
}
}
class ProjectOverviewDataSource : UITableViewSource
{
ProjectOverviewController controller;
public delegate void DetailProjectHandler(object sender, EventArgs e, int index);
public event DetailProjectHandler DetailProjectEvent;
public ProjectOverviewDataSource(ProjectOverviewController controller)
{
this.controller = controller;
}
// Returns the number of rows in each section of the table
public override nint RowsInSection(UITableView tableView, nint section)
{
return controller.Projects.Count;
}
//
// Returns a table cell for the row indicated by row property of the NSIndexPath
// This method is called multiple times to populate each row of the table.
// The method automatically uses cells that have scrolled off the screen or creates new ones as necessary.
//
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell(ProjectOverviewController.callHistoryCellId);
int row = indexPath.Row;
cell.TextLabel.Text = controller.Projects[row].Header;
return cell;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
var index = indexPath.Row;
if (DetailProjectEvent != null)
{
DetailProjectEvent(this, new EventArgs(), index);
}
tableView.DeselectRow(indexPath, true);
}
}
}
And finally the the ProjectViewController:
public sealed partial class ProjectViewController : UIPageViewController
{
private Project masterModel;
public ProjectViewController (IntPtr handle) : base (handle)
{
}
public void Initialize(Project masterModel)
{
if (masterModel == null)
{
throw new ArgumentNullException(nameof(masterModel));
}
this.masterModel = masterModel;
}
public override void ViewDidLoad()
{
Title = masterModel.Header;
CustomerField = new UITextField();
CustomerField.Text = masterModel.Customer;
ObjectAddressField = new UITextField();
ObjectAddressField.Text = masterModel.Customer;
}
}
Well there is an other strange behaviour I realized:
When I wanted to set a text for the CustomerField.Text and ObjectAddress.Text property I got a NullReferenceException that the two objects CustomerField and ObjectAddressField are null which leads me to the conclusion that there must a wrong initialization of the ProjectViewController.
If you have some other recommendations about my code, feel free to tell my, because as I said I´m a rookie with Xamarin and App-Development.
I have a requirement where I initially have a list of messages only ordered by date/time. The requirement if for the user to be able to click on a UISegmentedControl (list of 4 buttons) and be able to change the UITableView from a straight list to a grouped list (ie. grouped by category of message).
From what I've read, once the style is set on a UITableView you can not change it. So what is the best approach to satisfy this requirement? Kill the view and re-create with the appropriate style?
Not that it makes a huge difference, I am using Xamarin Studio and C#, targeting Mono 3.2.1 and iOS 6+
Rather than killing the view and re-instantiating, just maintain references to two UITableViews, one of each of the appropriate types. Toggle between them using your Controller class. The following simple example puts the toggling button in the same UIView as the table, which is probably not appropriate, but otherwise shows the technique:
public class ChangeableSource : UITableViewSource
{
public bool Grouped { get; set; }
public override int NumberOfSections(UITableView tableView)
{
if(Grouped)
{
return 4;
}
else
{
return 1;
}
}
public override int RowsInSection(UITableView tableview, int section)
{
return 3;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell("Default");
if(cell == null)
{
cell = new UITableViewCell(UITableViewCellStyle.Default, "Default");
}
cell.TextLabel.Text = String.Format("IndexPath {0} {1}", indexPath.Section, indexPath.Row);
return cell;
}
}
public class ToggleTableView : UIView
{
UITableView ungroupedView;
UITableView groupedView;
ChangeableSource changeableSource;
public void SetStyle(bool grouped)
{
changeableSource.Grouped = grouped;
if(changeableSource.Grouped)
{
ungroupedView.RemoveFromSuperview();
AddSubview(groupedView);
}
else
{
groupedView.RemoveFromSuperview();
AddSubview(ungroupedView);
}
}
public bool GetStyle()
{
return changeableSource.Grouped;
}
public ToggleTableView()
{
var btn = new UIButton(new RectangleF(10, 10, 150, 40));
btn.SetTitle("Change", UIControlState.Normal);
btn.TouchUpInside += (s,e) => ToggleStyle(this, new EventArgs());
var tvFrame = new RectangleF(0, 60, UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height - 60);
ungroupedView = new UITableView(tvFrame, UITableViewStyle.Plain);
groupedView = new UITableView(tvFrame, UITableViewStyle.Grouped);
AddSubview(btn);
AddSubview(ungroupedView);
changeableSource = new ChangeableSource();
changeableSource.Grouped = false;
ungroupedView.Source = changeableSource;
groupedView.Source = changeableSource;
}
public event EventHandler<EventArgs> ToggleStyle = delegate {};
}
public class TogglingTableController : UIViewController
{
public TogglingTableController() : base ()
{
}
public override void DidReceiveMemoryWarning()
{
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var view = new ToggleTableView();
view.ToggleStyle += (s,e) =>
{
view.SetStyle(! view.GetStyle());
};
this.View = view;
}
}
[Register ("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
UIWindow window;
TogglingTableController viewController;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
window = new UIWindow(UIScreen.MainScreen.Bounds);
viewController = new TogglingTableController();
window.RootViewController = viewController;
window.MakeKeyAndVisible();
return true;
}
}
public class Application
{
static void Main(string[] args)
{
UIApplication.Main(args, null, "AppDelegate");
}
}