How to receive feedback from classRenderer - c#

I needed to implement with a custom-renderer a map in my project developed with Xamarin Forms, here is how I did it:
public class MyMap : Map
{
public MyMap ():base(){ }
}
MapView(Xamarin Forms):
public class MapView:ContentPage
{
List<Filiale> list=jM.ReadData ();
MyMap myMap = new MyMap (//I'd like to pass list here);
var stack = new StackLayout ();
myMap.VerticalOptions = LayoutOptions.FillAndExpand;
myMap.HorizontalOptions = LayoutOptions.FillAndExpand;
stack.Children.Add (myMap);
Content = stack;
}
MapRenderer (Xamarin iOS):
[assembly: ExportRenderer (typeof (MyMap), typeof (MapiOS))]
namespace App.iOS
{
public class MapiOS : MapRenderer
{
private MKMapView NativeMap { get { return (this.NativeView as MapRenderer).Control as MKMapView; } }
public MapiOS ():base()
{
}
protected override void OnElementChanged (ElementChangedEventArgs<View> e)
{
base.OnElementChanged (e);
MyMapDelegate myMapDelegate = new MyMapDelegate ();
NativeMap.Delegate = myMapDelegate;
NativeMap.AddAnnotation(new MKPointAnnotation (){
Title=list[0].nome,
Coordinate = new CLLocationCoordinate2D (42.364260, -71.120824)
});
}
}
In the myMapDelegate class I also handled the click on a button displayed from clicking on the Pins like this:
public override void CalloutAccessoryControlTapped (MKMapView mapView, MKAnnotationView view, UIControl control){
//Call new Page
}
Now when this button is clicked I'd like to get back to Xamarin Forms and create a new page with it. How should I do it? Also How can I pass some object when I create the MyMap object?

Change your MyMap implementation to this
public class MyMap : Map
{
public static readonly BindableProperty LocationsProperty = BindableProperty.Create<MyMap, List<string>>(x => x.Locations, new List<string>());
public static readonly BindableProperty PinTappedCommandProperty = BindableProperty.Create<MyMap, Command>(x=>x.PinTapped, null);
public MyMap(List<string> locations)
{
Locations = locations;
PinTapped = new Command(async (x) =>
{
await Navigation.PopModalAsync();
await Navigation.PushAsync(SomeNewPage(x));
});
}
public List<string> Locations
{
get { return (List<string>)GetValue(LocationsProperty); }
set { SetValue(LocationsProperty, value); }
}
public Command PinTapped
{
get { return (Command) GetValue(PinTappedCommandProperty); }
set { SetValue(PinTappedCommandProperty, value);}
}
}
Now you can access Locations from your MapRenderer by changing it slightly:
public class MapiOS : ViewRenderer<MyMap, MKMapView>
{
protected override void OnElementChanged (ElementChangedEventArgs<MyMap> e)
{
base.OnElementChanged (e);
var map = e.NewElement; // Remember to check for null
var locations = map.Locations;
// Do what you want with locations
var cmd = Map.PinTapped; // Send this along to MyMapDelegate
var nativeMap = new MKMapView(); // Initiate with relevant parameters
SetNativeControl(nativeMap)
MyMapDelegate myMapDelegate = new MyMapDelegate (cmd); // Change constructor here
nativeMap.Delegate = myMapDelegate;
nativeMap.AddAnnotation(new MKPointAnnotation (){
Title=list[0].nome,
Coordinate = new CLLocationCoordinate2D (42.364260, -71.120824)
});
}
I have assumed that you have shown the map as a modal inside a NavigationPage or similar.
You now have the Command, so bring it along to your MyMapDelegate and use
x.PinTapped.Execute("YourParameter");

Related

How to access method from view inside a Xamarin Forms custom renderer?

I have the following code:
public partial class PhrasesFrameRendererClass : Frame
{
.....
void getRandomWords() {
// more code here that involves getting random numbers
// and updating a grid's bindingcontext
}
}
In my custom renderer I want to be able to call the getRandomWords on swipe left gesture like below:
public class PhraseFrameCustomRenderer : FrameRenderer
{
UISwipeGestureRecognizer leftSwipeGestureRecognizer;
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
leftSwipeGestureRecognizer = new UISwipeGestureRecognizer();
leftSwipeGestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
leftSwipeGestureRecognizer.NumberOfTouchesRequired = 1;
leftSwipeGestureRecognizer.AddTarget((obj) =>
{
// Call getRandomWords() here
});
}
}
Is this possible? Any ideas on how this could be done?
base.OnElementChanged(e);
leftSwipeGestureRecognizer = new UISwipeGestureRecognizer();
leftSwipeGestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
leftSwipeGestureRecognizer.NumberOfTouchesRequired = 1;
leftSwipeGestureRecognizer.AddTarget((obj) =>
{
// Call getRandomWords() here
var frame = Element as PhrasesFrameRendererClass ;
if(frame!=null){
frame.getRandomWords();
}
});
You can create a BindableProperty of type Command in your custom frame class, call that Command from your renderer and bind your ViewModel's getRandomWords method as a Command
//Your custom control in your PCL project
public partial class PhrasesFrameRendererClass : Frame
{
public static readonly BindableProperty SwipeLeftCommandProperty =
BindableProperty.Create(nameof(SwipeLeftCommand), typeof(ICommand), typeof(PhrasesFrameRendererClass ), null);
public ICommand SwipeLeftCommand
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
}
//Your custom control renderer
public class PhraseFrameCustomRenderer : FrameRenderer
{
UISwipeGestureRecognizer leftSwipeGestureRecognizer;
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
leftSwipeGestureRecognizer = new UISwipeGestureRecognizer();
leftSwipeGestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
leftSwipeGestureRecognizer.NumberOfTouchesRequired = 1;
leftSwipeGestureRecognizer.AddTarget((obj) =>
{
var myFrame = Element as PhrasesFrameRendererClassl
if(myFrame != null){
if(myFrame.SwipeLeftCommand != null && myFrame.SwipeLeftCommand.CanExecute()){
myFrame.SwipeLeftCommand.Execute();
}
}
});
}
}
//Your ViewModel
public class PhrasesViewModel{
public Command GetRandomWordsCommand {get;set;}
public PhrasesViewModel(){
GetRandomWordsCommand = new Command(ExecuteGetRandomWords);
}
private void ExecuteGetRandomWords(){
//Your method goes here
}
}
//Your XAML
<yourControls:PhrasesFrameRendererClass SwipeLeftCommand="{Binding GetRandomWordsCommand }"/>
It may seem more complicated this way, but using commands allows you to separate your application code (Such as getting random phrases) from your rendering code

Xamarin.IOS Add a Splitview to Tabbed Application

I am programming an Application on Xamarin.IOS. I have a Tabbed Layout as for my Main View and all the UIElements are set in the Storyboard. Now, what i need to do, because i want to implement IAds,
i want my Application to be a Split view, with the IAd View Controller as the Detail View, and my usual TabbedLayout as the main view.
So, yeah, my precise question would be:
As all my Layout is loaded from the Storyboard: How can i pass an instance of the View that gets loaded usually to my SplitViewController?
Any tips you can give, are helpful, i really need this for my work!!
To provide some Code:
I am trying to load my initial View (Which is the TabBarController) like this in the AppDelegate.cs:
SplitViewContoller splitView = new SplitViewContoller();
IADViewController iAdVC = new IADViewController (splitView);
Console.WriteLine ("Root" + Window.RootViewController);
Window.RootViewController = iAdVC;
That is working fine, except that the view that usually appears doesn't get loaded... The IAD shows up but the rest of the screen is an empty TabBarController.
Here is my SplitViewController:
public class SplitViewContoller : UISplitViewController
{
UIViewController masterView, detailView;
public SplitViewContoller () : base()
{
// create our master and detail views
masterView = new TabBarController();
detailView = new IADViewController (new TabBarController());
// create an array of controllers from them and then
// assign it to the controllers property
ViewControllers = new UIViewController[]
{ masterView, detailView }; // order is important
}
public override bool ShouldAutorotateToInterfaceOrientation
(UIInterfaceOrientation toInterfaceOrientation)
{
return true;
}
}
The View that i want to be at the bottom of my screen:
(See Monotouch.Dialog and iAds for details)
public partial class IADViewController : UIViewController
{
private UIViewController _anyVC;
private MonoTouch.iAd.ADBannerView _ad;
public IADViewController (UIViewController anyVC)
{
_anyVC = anyVC;
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
View.AddSubview (_anyVC.View);
Version version = new Version (MonoTouch.Constants.Version);
if (version > new Version (6,0))
{
try {
_ad = new MonoTouch.iAd.ADBannerView (MonoTouch.iAd.ADAdType.Banner);
_ad.Hidden = true;
_ad.FailedToReceiveAd += HandleFailedToReceiveAd;
_ad.AdLoaded += HandleAdLoaded;
View.BackgroundColor = UIColor.Clear;
_anyVC.View.Frame = View.Bounds;
View.AddSubview (_ad);
} catch {
}
} else {
Resize ();
}
}
public override void DidRotate (UIInterfaceOrientation fromInterfaceOrientation)
{
base.DidRotate (fromInterfaceOrientation);
Resize ();
}
public override void ViewDidAppear (bool animated)
{
base.ViewDidAppear (animated);
Resize ();
}
void Resize ()
{
UIView.Animate (.25,
() => {
if (_ad !=null && _ad.Hidden == false) {
_anyVC.View.Frame = new RectangleF (0, 0, this.View.Bounds.Width, this.View.Bounds.Height - _ad.Frame.Height);
} else {
_anyVC.View.Frame = View.Bounds;
}
});
if(_ad!=null)
_ad.Frame = new RectangleF (0, _anyVC.View.Bounds.Height, this.View.Bounds.Width, _ad.Frame.Height);
}
void HandleAdLoaded (object sender, EventArgs e)
{
if (_ad == null)
return;
_ad.Hidden = false;
Resize ();
}
void HandleFailedToReceiveAd (object sender, AdErrorEventArgs e)
{
if (_ad == null)
return;
_ad.Hidden = true;
Resize ();
}
}
If anyone else need this, i solved it with the following lines of code now.
Storyboard = UIStoryboard.FromName ("MainStoryboard", null);
initialViewController = Storyboard.InstantiateInitialViewController () as UITabBarController;
// If you have defined a root view controller, set it here:
IADViewController iAdVc = new IADViewController (initialViewController);
Window.RootViewController = iAdVc;
// make the window visible
Window.MakeKeyAndVisible ();

Custom MenuItemPanel (How add Items on Design Time?)

The problem is as follows:
In my User Control i have Label List (List ) which fills adding Labels which UC through a "Smart Tag" (using a DesignerActionMethodItem).
The problem is that it works correctly when we are in design time, for example, I add 3 items at design time, but when I test the application these items disappear as if they had never added.
P.S.:
I have:
MyControl class,
[Designer(typeof(MenuItemPanelDesigner ))]
public partial class MenuItemPanel : UserControl
{
private List<Label> _listaItems;
public MenuItemPanel()
{
InitializeComponent();
}
public List<Label> ListaItems
{
get
{
if (this._listaItems == null)
{
this._listaItems = new List<Label>();
}
return this._listaItems;
}
}
public void AgregarItem()
{
Label nuevoItem = new Label();
nuevoItem.Text = "Item " + this._listaItems.Count;
nuevoItem.AutoSize = false;
nuevoItem.TextAlign = ContentAlignment.MiddleCenter;
nuevoItem.Cursor = Cursors.Hand;
this.ListaItems.Add(nuevoItem);
this.Controls.Add(nuevoItem);
nuevoItem.Dock = DockStyle.Top;
nuevoItem.Height = 50;
}
}
MyControlDesigner class
class MenuItemPanelDesigner : System.Windows.Forms.Design.ControlDesigner
{
private DesignerActionListCollection actionLists;
public override DesignerActionListCollection ActionLists
{
get
{
if (null == actionLists)
{
actionLists = new DesignerActionListCollection();
actionLists.Add(new MenuItemPanelDesignerActionList(this.Component));
}
return actionLists;
}
}
}
and MyControlDesignerActionList
class MenuItemPanelDesignerActionList : DesignerActionList
{
private MenuItemPanel colUserControl;
private DesignerActionUIService designerActionUISvc = null;
//The constructor associates the control with the smart tag list.
public MenuItemPanelDesignerActionList(IComponent component) : base(component)
{
this.colUserControl = (MenuItemPanel)component;
this.designerActionUISvc = (DesignerActionUIService)GetService(typeof(DesignerActionUIService));
}
// Implementation of this abstract method creates smart tag items,
// associates their targets, and collects into list.
public override DesignerActionItemCollection GetSortedActionItems()
{
DesignerActionItemCollection items = new DesignerActionItemCollection();
//Define static section header entries.
items.Add(new DesignerActionHeaderItem("Items"));
items.Add(new DesignerActionMethodItem(this,"AgregarItem","Agregar Item"));
return items;
}
// Metodos
public void AgregarItem()
{
this.colUserControl.AgregarItem();
}
}

Why cannot change Monotouch.Dialog.TableView.Background color

Why cannot change MonoTouch.Dialog.TableView.Background color ? I am using the Elements API of MonoTouch.Dialog. It's still gray!
class MyDialogViewController : DialogViewController {
public MyDialogViewController (RootElement root) : base (root)
{
}
public override void LoadView ()
{
base.LoadView ();
TableView.BackgroundColor = UIColor.Clear;
UIImage background = UIImage.FromFile ("Mybackground.png");
ParentViewController.View.BackgroundColor = UIColor.FromPatternImage (background);
}
}
public partial class MyVC: UINavigationController
{
public void CreateTestUI()
{
var menu = new RootElement("MyMenu"){
new Section ("test"){
new StringElement("Test", delegate() { }), ...
var dv = new MyDialogViewController (menu) {
Autorotate = true
};
// add the nav controller to the window
this.PushViewController (dv, true);
}
}
in iPad,must add this line: TableView.BackgroundView = null;
Now it works well,thanks poupou, thanks to all.
public override void LoadView()
{
base.LoadView();
TableView.BackgroundView = null;
TableView.BackgroundColor = UIColor.Black;
}

Adding PushPins in Bing Maps

I am trying to add pushpins to a Bing Map. The push pins are got from a JSON feed. I would like to get something like this:
My code does not work for the first time alone and I cant understand why.
My map ViewModel is
public class MapViewModel : INotifyPropertyChanged
{
public static ObservableCollection<PushpinModel> pushpins = new ObservableCollection<PushpinModel>();
public static ObservableCollection<PushpinModel> Pushpins
{
get { return pushpins; }
set { pushpins = value; }
}
}
The Map xaml cs is:
//Map.xaml.cs
public partial class Map : PhoneApplicationPage
{
#define DEBUG_AGENT
private IGeoPositionWatcher<GeoCoordinate> watcher;
private MapViewModel mapViewModel;
public Map()
{
InitializeComponent();
mapViewModel = new MapViewModel();
this.DataContext = mapViewModel;
}
private void page_Loaded(object sender, RoutedEventArgs e)
{
if (watcher == null)
{
#if DEBUG_AGENT
watcher = new Shoporific.My.FakeGPS();
#else
watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.Default);
#endif
}
watcher.Start();
mapViewModel.Center = watcher.Position.Location;
PushpinModel myLocation = new PushpinModel() { Location = mapViewModel.Center, Content = "My Location" };
MapViewModel.Pushpins.Add(myLocation);
myLocation.RefreshNearbyDeals();
watcher.Stop();
}
}
Finally, the PushPinModelClass:
public class PushPinModel
{
public void RefreshNearbyDeals()
{
System.Net.WebClient wc = new WebClient();
wc.OpenReadCompleted += wc_OpenReadCompleted;
wc.OpenReadAsync(" a valid uri");
}
void wc_OpenReadCompleted(object sender, System.Net.OpenReadCompletedEventArgs e)
{
var jsonStream = e.Result;
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(Deal[]));
Deal[] deals = (ser.ReadObject(jsonStream) as Deal[]);
if (deals.Any())
{
var currentLocation = MapViewModel.Pushpins.Where(pin => pin.Content == "My Location");
MapViewModel.Pushpins = new ObservableCollection<PushpinModel>();
foreach (var deal in deals)
MapViewModel.Pushpins.Add(new PushpinModel()
{
Content = deal.Store,
Location = new GeoCoordinate(deal.Location.Latitude, deal.Location.Longtitude),
Offers = deal.Offers,
});
}
}
}
I am a bit confused that the Pushpins except "My Location" dont show up only on the first time. They appear as expected the second time onwards(If I navigate back and then move to the Map screen again).
Inside wc_OpenReadCompleted, you are re-instantiating MapViewModel.Pushpins.
Only call the constructor to an ObservableCollection once (in your case within the MainViewModel). Calling it again messes up the binding that I assume you have in your xaml page.
I believe that you should either remove that line in the PushpinViewModel or call MainViewModel.Pushpins.Clear() instead (depending on what you are trying to accomplish).

Categories

Resources