I'm trying to create my first iOS project. I have quite well experiences with C#. I receive data with categories and articles. But I don't know, how many subcategories I will get, before the articles will be listed. The browsing though the subcategories should be done via TableView. When a User clicks a category, the subcategories (level 1) should be displayed in a tableView. Then, after touching a subcategory, the subcategories (level 2) of present should be displayed (and so on). Lastly, when there are no further subcategories, the article-list should be displayed. After touching one article, a new ViewController with the article-data should be displayed.
My question is, how to handle the navigation through the subcategories and creating segues or sth. like that. I know, that I can't use storyboard for this, because I don't know the number of subcategories and they also differ.
How can I achieve this dynamic navigation? Can you give me an example? I know how to populate data to the tableView. Only the dynamic navigation is the problem.
I now have a solution. Can someone please check, if I am doing it correctly not that I don't violate programming patterns or guidelines.
In short: In TableSource-Class I access the Storyboard and dynamically create and show the same Controller which actually is presented. But before presenting the new one, I declare an other source-class. So the TableView is used for displaying Addressbooks and Addresscards, the UIViewController is used for displaying the carddata. These two controllers are connected via segue.
Here are my Controller and both Source-Classes:
AddressbooksController.cs
public AddressbooksController (IntPtr handle) : base (handle)
{
displayModel = "addressbooks";
}
private void loadAddressbooks()
{
var AppDelegate = UIApplication.SharedApplication.Delegate as AppDelegate;
addressbooks = AppDelegate.api.DeserializeEntities<YAddressbook>(Task.Run(async () => await AppDelegate.api.Get("Yaddressbooks")).Result);
}
public void loadAddresscards()
{
var AppDelegate = UIApplication.SharedApplication.Delegate as AppDelegate;
addresscards = AppDelegate.api.DeserializeEntities<Ycard>(Task.Run(async () => await AppDelegate.api.Get("Ycards")).Result);
}
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
{
base.PrepareForSegue(segue, sender);
// do first a control on the Identifier for your segu
if (segue.Identifier.Equals("segueShowAddress"))
{
var viewController = (AddresscardController)segue.DestinationViewController;
viewController.card = Card2Display;
}
}
public override void ViewDidLoad()
{
if (displayModel == "addressbooks")
{
loadAddressbooks();
tableView.Source = new AddressbooksTableSource(addressbooks.data, this);
this.NavigationController.Title = "Addressbücher";
}
else if (displayModel == "addresscards")
{
loadAddresscards();
tableView.Source = new AddresscardsTableSource(addresscards.data, this);
this.NavigationController.
}
base.ViewDidLoad();
}
AddressbooksTableSource
public class AddressbooksTableSource: UITableViewSource
{
private List<YAddressbook> addressbooks;
private string cellIdentifier = "AddressbooksCell";
private UINavigationController navigationController;
public AddressbooksTableSource(List<YAddressbook> books, AddressbooksController ab)
{
addressbooks = books;
this.navigationController = ab.ParentViewController as UINavigationController;
Console.WriteLine(ab.displayModel);
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
Console.WriteLine("Row selected" + addressbooks[indexPath.Row].displayname);
UIStoryboard Storyboard = UIStoryboard.FromName("Main", null);
AddressbooksController newab = Storyboard.InstantiateViewController("AddressbooksViewController") as AddressbooksController;
newab.displayModel = "addresscards";
navigationController.PushViewController(newab, true);
tableView.DeselectRow(indexPath, true);
}
....
}
AddresscardsTableSource
public class AddresscardsTableSource: UITableViewSource
{
private List<Ycard> addresscards;
UINavigationController navigationController;
AddressbooksController ab;
string cellIdentifier = "AddresscardCell";
public AddresscardsTableSource(List<Ycard> cards, AddressbooksController vc)
{
addresscards = cards;
navigationController = vc.ParentViewController as UINavigationController;
ab = vc;
//navigationController = tableview;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
Console.WriteLine("Row selected" + addresscards[indexPath.Row].carddata);
//UIStoryboard Storyboard = UIStoryboard.FromName("Main", null);
ab.Card2Display = addresscards[indexPath.Row];
ab.PerformSegue("segueShowAddress", indexPath);
//AddresscardController ab = Storyboard.InstantiateViewController("AddresscardViewController") as AddressbooksController;
//ab.TableView.Source = this;
//navigationController.PushViewController(ab, true);
tableView.DeselectRow(indexPath, true);
}
.....
}
It works. But am I doing it correctly? Thanks
Related
I have table view with images in cells
But when data is shown in TableView and I scroll it, it not smooth. But all images are downloaded I think.
Here is Code for TableView source
public class ExperienceSource : UITableViewSource
{
//UINavigationController instance, to pass it to constructor
private UINavigationController primNav { get; set; }
private UITableView tableView { get; set; }
List<Experience> TableItems;
ExperienceTableViewController owner;
public ExperienceSource(List<Experience> items,ExperienceTableViewController owner, UINavigationController nav)
{
TableItems = items;
this.owner = owner;
primNav = nav;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
if (TableItems.Count == 0)
{
var noDataLabel = new UILabel
{
Text = "No Experiences at your location at this time. Try to change destination",
TextColor = UIColor.Black,
TextAlignment = UITextAlignment.Center,
LineBreakMode = UILineBreakMode.WordWrap,
Lines = 0
};
tableview.BackgroundView = noDataLabel;
tableview.SeparatorStyle = UITableViewCellSeparatorStyle.None;
return TableItems.Count;
}
else
{
tableview.SeparatorStyle = UITableViewCellSeparatorStyle.SingleLine;
return TableItems.Count;
}
}
public override async void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
var selectedExperience = await ExperienceMethods.GetSelectedTour(TableItems[indexPath.Row].id);
if (selectedExperience == "Saved")
{
ExperienceDetailViewController ExperienceDetailController = primNav.Storyboard.InstantiateViewController("ExperienceDetailViewController") as ExperienceDetailViewController;
primNav.PushViewController(ExperienceDetailController, true);
}
else
{
UIAlertController okAlertController = UIAlertController.Create("", "Cannot select this experience", UIAlertControllerStyle.Alert);
okAlertController.AddAction(UIAlertAction.Create("OK", UIAlertActionStyle.Default, null));
}
tableView.DeselectRow(indexPath, true);
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell("cell_id", indexPath) as ExperienceCell;
Experience item = TableItems[indexPath.Row];
cell.UpdateCell(item);
return cell;
}
And I have code for my custom cell in this class
public partial class ExperienceCell : UITableViewCell
{
public ExperienceCell (IntPtr handle) : base (handle)
{
}
internal void UpdateCell(Experience experience)
{
UIImage picture;
try
{
var image_url = "https://xplorpal.com/" + experience.cover_image.img_path + "/300x300/" + experience.cover_image.img_file;
using (var url = new NSUrl(image_url))
using (var data = NSData.FromUrl(url))
picture = UIImage.LoadFromData(data);
ExperienceImage.Image = picture;
}
catch { }
finally
{
ExperienceTitle.Text = experience.title;
ExperiencePrice.Text = "$" + experience.price;
}
}
}
Where can be my problem and how I can optimize scroll of this TableView?
Thank's for help.
I am not familiar with using NSData.FromUrl method inside a TableViewCell, but handling remote images manually is most of the time a bad idea.
You should consider using a library like SDWebImage, that will handle downloading, caching, and assigning the image to your UIImage for you.
I have tried to look out for an answer to the behavior of my views but I seem not find any question or solution related to it. My recycler views seemed to be set up well. I just realized that my app is not responding in the right way to the OnClickListeners.
I have set up toasts in my adapter for the recycler view click events. When i have 10 views. When i click on a view, it gives a text of another view. It seems it randomly gives me the text of another view amongst the 9 remaining views. What could be the cause of this?
Activity
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
FrameLayout content = (FrameLayout)FindViewById(Resource.Id.content_frame);
LayoutInflater.Inflate(Resource.Layout.Main, content);
setUpRecyclerView();
}
public void setUpRecyclerView(){
rv = FindViewById<RecyclerView>(Resource.Id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.Orientation = LinearLayoutManager.Vertical;
layoutManager.ReverseLayout = true;
layoutManager.StackFromEnd = true;
rv.HasFixedSize = true;
rv.SetLayoutManager(layoutManager);
}
Adapter
public class FeedViewHolder : RecyclerView.ViewHolder, View.IOnClickListener, View.IOnLongClickListener
{
public FeedViewHolder(View itemView):base(itemView)
{
//binding of variables here
itemView.SetOnClickListener(this);
itemView.SetOnLongClickListener(this);
}
public void OnClick(View v)
{
itemClickListener.OnClick(v, AdapterPosition, false);
}
public bool OnLongClick(View v)
{
itemClickListener.OnClick(v, AdapterPosition, true);
return true;
}
public class FeedAdapter : RecyclerView.Adapter, ItemClickListener
{
public FeedAdapter(RssObject rssObject, Context mContext)
{
this.mContext = mContext;
this.inflater = LayoutInflater.From(mContext);
activity = (MainActivity)mContext;
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
hold = holder as FeedViewHolder;
//binding
hold.itemClickListener = this;
}
public void OnClick(View view, int position, bool isLongClick)
{
Toast.MakeText(activity, "Main text : " + hold.txtContent.Text, ToastLength.Long).Show();
}
public override int ItemCount
{
get { return rssObject.items.Count; }
}
}
}
}
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 my fragments inside view page but I would like to reload the data on the tab selected.I tried returning PositionNone in the GetItemPosition method in my FragmentPagerAdapter but it does not work.
I tried adding notifyDataSetChanged(); on tab selected but it throws nullpointer exception.
I even tried setting the viewPager.setOffscreenPageLimit
I managed to find the link :
Replace Fragment inside a ViewPager
but it is on java.
I have managed to convert most of the code to c# but gets stuck on the commented code. I am not sure how to call the listener and instantiate the class at the same time? There is a lot of similar question but unfortunately not one in Xamarin Android.
I need to do this in xamarin android. Any help will be greatly appreciated.
This is my FragmentAdapter
public class TabsFragmentPagerAdapter : FragmentPagerAdapter
{
private readonly Android.Support.V4.App.Fragment[] fragments;
static readonly int NUM_ITEMS = 2;
private readonly Android.Support.V4.App.FragmentManager FragmentManager;
private Android.Support.V4.App.Fragment mFragmentAtPos0;
private readonly ICharSequence[] titles;
public TabsFragmentPagerAdapter(Android.Support.V4.App.FragmentManager fm, Android.Support.V4.App.Fragment[] fragments, ICharSequence[] titles) : base(fm)
{
this.fragments = fragments;
this.titles = titles;
}
public override int Count
{
get
{
return fragments.Length;
}
}
public override int GetItemPosition(Java.Lang.Object objectValue)
{
if (objectValue.GetType() == typeof(startJobFrag) && mFragmentAtPos0.GetType() == typeof(jobCaptureFrag))
{
return PositionNone;
}
return PositionUnchanged;
}
public override ICharSequence GetPageTitleFormatted(int position)
{
return titles[position];
}
public override Android.Support.V4.App.Fragment GetItem(int position)
{
if (position == 0)
{
if (mFragmentAtPos0 == null)
{
//not working
}
return mFragmentAtPos0;
}
else
{
return new jobCaptureFrag();
}
}
This is my Actvity
void FnInitTabLayout()
{
var fragments = new Android.Support.V4.App.Fragment[]
{
new frag 1(),
new frag 2(),
new frag 3(),
};
//Tab title array
var titles = CharSequence.ArrayFromStringArray(new[] {
"Page 1",
"Page 2",
"Page 3",
});
var viewPager = FindViewById<ViewPager>(Resource.Id.viewpager);
//viewpager holding fragment array and tab title text
//viewPager.
viewPager.Adapter = new TabsFragmentPagerAdapter(SupportFragmentManager, fragments, titles);
// Give the TabLayout the ViewPager
tabLayout.SetupWithViewPager(viewPager);
viewPager.PageSelected += (object sender, ViewPager.PageSelectedEventArgs e) =>
{
//FnInitTabLayout();
if(e.Position == 0)
{
//handle tab click/selected
}
};
}
public interface FirstPageFragmentListener
{
void onSwitchToNextFragment();
}
Fragment
public class Fragment1: Android.Support.V4.App.Fragment
{
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your fragment here
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Use this to return your custom view for this Fragment
// return inflater.Inflate(Resource.Layout.YourFragment, container, false);
View view = inflater.Inflate(Resource.Layout.myview, container, false);
}
}
I managed to solve it on my own.
In My activity I called NotifyDataSetChanged() on Pageselected event
Activity
ViewPager.PageSelected += (object sender, ViewPager.PageSelectedEventArgs e) =>
{
if (e.Position == 1)
{
_pager.Adapter.NotifyDataSetChanged();
}
};
Then I changed my Adapter from FragmentPagerAdapter to FragmentStatePagerAdapter and override the GetItemPosition.
Adapter
public class TabsFragmentPagerAdapter : FragmentStatePagerAdapter
{
public override int GetItemPosition(Java.Lang.Object objectValue)
{
return PositionNone;
}
}
Refresh a specific tab - inside the Adapter
public override Android.Support.V4.App.Fragment GetItem(int position)
{
if(position == 1) //second tab selected
{
NotifyDataSetChanged();
}
return fragments[position];
}
I'm sure there's a better way to do it but this works.
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");
}
}