Daniel Hindrikes: Xamarin.Forms: Add content to TitleView in your iOS app
Gerald Versluis: Xamarin.Forms: from zero to hero – part 2
Matthew Soucoup: Cold Hard Data Cache – Or Saving Our Users Money!
Tomasz Cielecki: iOS WebView insets
My problem was that I had a UIWebView, which kept laying itself out underneath the NavigationBar in my controller. A quick fix would be to just set edges for the extended layout to none like so:
However, this will make you lose the nice effect of views scrolling behind the NavigationBar, for instance if the web page you are displaying scrolls.
Enter insets. As the name kind of indicates you add some spacing into your view. There is a property called AutomaticallyAdjustsScrollViewInsets. However, for some reason it does not do anything in my case, so I had to manually adjust the inset, which I did in ViewWillLayoutSubviews as in ViewDidLoad the TopLayoutGuide is not ready yet and will give you 0 for its Length. Basically this is what I had to do:
This tells both the UIWebView’s internal ScrollView and the scroll bar that you want some space in the top equals to the height of the NavigationBar.
Pavel Sich: New components versions
New 64 bit versions of AuthohideNavigationBar and AnimatedLiveButton are now available on Xamarin component store. Happy Coding.
The post New components versions appeared first on IOS BITS LTD | Mobile application development.
Adam Kemp: Decoupling Views In Multi-Screen Sequences
In my previous post I explained how to decouple individual views and why that is a good idea. In this post I will take that idea further and explain how to use this concept in more advanced UX scenarios involving multi-screen sequences.
Motivation
As a summary, the benefits of decoupling views are increased flexibility and allowing for more code reuse. For instance, a particular type of view may be used in multiple parts of your application in slightly different scenarios. If that view makes assumptions about where it fits within the whole app then it would be difficult to reuse that view in a different part of the app.
Still, at some level in your application you need to build in some kind of knowledge of which view is next. In the last post I gave a basic example where that knowledge lived in the Application
class. There are many situations in which the Application
class may be the best place for this kind of app-wide navigation logic, but some situations are more advanced and require a more sophisticated technique.
For example, it is also common to have a series of views within an app that always go together, but that sequence as a whole may be launched from different parts of the application. On iOS this kind of reusable sequence of views can be represented in a Storyboard1, but we can achieve the same result in code.
An Example
As an example let’s consider a sequence of views for posting a picture to a social network:
- Choose a picture from a library or choose to take a new picture.
- If the user chose to take a new picture then show the camera view.
- After the user has either chosen a picture or taken a new picture he can add a comment.
- The picture is posted.
At any point during this process the user should also have the option to cancel, which should return the user back to where he started.
Here are some questions to consider when implementing this UX flow:
- How can we handle the cancel button in a way that avoids code duplication?
- How can we avoid code duplication for the various parts of the app that might want to invoke this sequence? For instance, perhaps you can post a picture either to your own profile or on someone else’s profile or in a comment or in a private message.
- How can we allow for flexibility such that different parts of the app can do different things with the chosen picture/comment?
The first two questions are about code reuse, which is one of our goals. We want to avoid both having these individual screens duplicate code to accomplish the same thing, and we also want to avoid duplication of code from elsewhere in our app. The last question is about how we can decouple this code itself from the act of using the results of the sequence (i.e., the picture and the comment). This is important because each part of the app that might use this probably has to do slightly different things with the results.
Creating the Views
The example flow has three unique screens:
- A screen that lets the user choose an image or choose to take a new picture.
- A screen for taking a picture.
- A screen for entering a comment.
As per my last post, each of these views should be written to be agnostic about how it’s used. There may be yet another part of the application that allows for editing a comment on an existing post, and you probably want to reuse the same view (#3) for that use case. Therefore you shouldn’t make any assumptions when implementing that view about how it will be used.
To accomplish this each view could be written with events for getting the results. Their APIs might look like this:
public class ImageEventArgs : EventArgs
{
public Image Image { get; private set; }
public ImageEventArgs(Image image)
{
Image = image;
}
}
public class CommentEventArgs : EventArgs
{
public string Comment { get; private set; }
public CommentEventArgs(string comment)
{
Comment = comment;
}
}
public class ImagePickerPage : ContentPage
{
public event EventHandler TakeNewImage;
public event EventHandler<ImageEventArgs> ImageChosen;
// ...
}
public class CameraPage : ContentPage
{
public event EventHandler<ImageEventArgs> PictureTaken;
// ...
}
public class ImageCommentPage : ContentPage
{
public event EventHandler<CommentEventArgs> CommentEntered;
public ImageCommentPage(Image image)
{
// ...
}
// ...
}
Constructing the Sequence
Now that we have our building blocks we need to put it all together. To do that we will create a new class that represents the whole sequence. This new class doesn’t need to be a view itself. Instead, it is just an object that manages the sequence. It will be responsible for creating each page as needed, putting them on the screen, and combining the results. Its public API might look like this:
public class CommentedImageSequenceResults
{
public static CommentedImageSequenceResults CanceledResult = new CommentedImageSequenceResults();
public bool Canceled { get; private set; }
public Image Image { get; private set; }
public string Comment { get; private set; }
public CommentedImageSequenceResults(Image image, string comment)
{
Image = image;
Comment = comment;
}
private CommentedImageSequenceResults()
{
Canceled = true;
}
}
public class CommentedImageSequence
{
public static Task<CommentedImageSequenceResults> ShowAsync(INavigation navigation)
{
// ...
}
// ...
}
Notice that in this case I’ve chosen to simplify the API by using a Task<T>
instead of multiple events. This plays nicely with C#’s async
/await
feature. I could have done the same with each of the individual views as well, but I wanted to show both approaches. Here is an example of how this API could be used:
public class ProfilePage : ContentPage
{
// ...
private async void HandleAddImageButtonPressed(object sender, EventArgs e)
{
var results = await CommentedImageSequence.ShowAsync(Navigation);
if (!results.Canceled)
{
PostImage(results.Image, results.Comment);
}
}
}
Of course you could have similar code elsewhere in the app, but what you do with the results would be different. That satisfies our requirements of flexibility and avoiding code duplication.
Now let’s look at how you would actually implement the sequence:
public class CommentedImageSequence
{
private readonly TaskCompletionSource<CommentedImageSequenceResults> _taskCompletionSource = new TaskCompletionSource<CommentedImageSequenceResults>();
private readonly NavigationPage _navigationPage;
private readonly ToolbarItem _cancelButton;
private Image _image;
private CommentedImageSequence()
{
_cancelButton = new ToolbarItem("Cancel", icon: null, activated: HandleCancel);
_navigationPage = new NavigationPage(CreateImagePickerPage());
}
private void AddCancelButton(Page page)
{
page.ToolbarItems.Add(_cancelButton);
}
private ImagePickerPage CreateImagePickerPage()
{
var page = new ImagePickerPage();
AddCancelButton(page);
page.TakeNewImage += HandleTakeNewImage;
page.ImageChosen += HandleImageChosen;
return page;
}
private CameraPage CreateCameraPage()
{
var page = new CameraPage();
AddCancelButton(page);
page.PictureTaken += HandleImageChosen;
return page;
}
private ImageCommentPage CreateImageCommentPage()
{
var page = new ImageCommentPage(_image);
AddCancelButton(page);
page.CommentEntered += HandleCommentEntered;
return page;
}
private async void HandleTakeNewImage(object sender, EventArgs e)
{
await _navigationPage.PushAsync(CreateCameraPage());
}
private async void HandleImageChosen(object sender, ImageEventArgs e)
{
_image = e.Image;
await _navigationPage.PushAsync(CreateImageCommentPage());
}
private void HandleCommentEntered(object sender, CommentEventArgs e)
{
_taskCompletionSource.SetResult(new CommentedImageSequenceResults(_image, e.Comment));
}
private void HandleCancel()
{
_taskCompletionSource.SetResult(CommentedImageSequenceResults.CanceledResult);
}
public static async Task<CommentedImageSequenceResults> ShowAsync(INavigation navigation)
{
var sequence = new CommentedImageSequence();
await navigation.PushModalAsync(sequence._navigationPage);
var results = await sequence._taskCompletionSource.Task;
await navigation.PopModalAsync();
return results;
}
}
Let’s summarize what this class does:
- It creates the
NavigationPage
used for displaying the series of pages and allowing the user to go back, and it presents that page (modally). - It creates the cancel button that allows the user to cancel. Notice how only one cancel button needed to be created, and it is handled in only one place. Code reuse!
- It creates each page in the sequence as needed and pushes it onto the
NavigationPage
‘s stack. - It keeps track of all of the information gathered so far. That is, once a user has taken or captured an image it holds onto that image while waiting for the user to enter a comment. Once the comment is entered it can return both the image and the comment together.
- It dismisses everything when done.
Now we can easily show this whole sequence of views from anywhere in our app with just a single line of code. If we later decide to tweak the order of the views (maybe we decide to ask for the comment first for some reason) then we don’t have to change any of those places in the app that invoke this sequence. We just have to change this one class. Likewise, if we decide that we don’t want a modal view and instead we want to reuse an existing NavigationPage
then we just touch this one class. That’s because all of the navigation calls for this whole sequence (presenting the modal navigation page, pushing views, and popping the modal) are in a single, cohesive class.
Summary
This technique can be used for any self-contained sequence of views within an application, including the app as a whole if you wanted. You can also compose these sequences if needed (that is, one sequence could reuse another sequence as part of its implementation). This is a powerful pattern for keeping code decoupled and cohesive. Anytime you find yourself wanting to put a call to PushAsync
or PushModalAsync
(or the equivalent on other platforms) within a view itself you should stop and think about how you could restructure that code to keep all of the navigation in one place.
-
I do not actually recommend using iOS storyboards for multiple reasons, which I may eventually get around to documenting in a blog post. ↩
Daniel Hindrikes: Get started with Xamarin.Forms for Windows
Johan Karlsson: Navigation from a ListView
The problem to be solved
The solution
The solution to this is simple, just hook up to the ItemSelected event for the ListView and set the SelectedItem property to null.
So the short version of you quick-googlers would be the following line of code. (in the View)
// Reset the selected item to make it selectable again
duckListView.ItemSelected += (s, e) => {
duckListView.SelectedItem = null;
And the navigation should be done in the ViewModel
public Duck SelectedDuck
{
set
{
if (value != null)
{
// IoC omitted, we should really get someone else to
// create these objects for us.
var viewModel = new DuckViewModel() { Duck = value };
var page = new DuckView(viewModel);
_navigation.PushAsync(page);
}
}
}
The more verbose version
You could also navigate directly from this event handler, but you should feel it deep in your heart that that is just wrong. Instead we handle navigation in the ViewModel. I’ve created a sample project to do this. Also, I’m doing this without any additional framework that would handle navigation for you so that’s why I need to provide my ViewModel with a navigation interface.
I’ll present each file to you below or just download the sample solution from here.
The Model
{
public string Name
{
get;
set;
}
}
The ViewModel (s)
We’ve got two ViewModels, but it’s really only the MainViewModel that’s interesting. It initializes it’s own data, which usually should be done async from another source. It doesn’t implement INotifyPropertyChanged either, as is should but for this sample it’s good enough.
What to focus on is the SelectedDuck property that handles the navigation. We only implement a setter for this since we reset the selected item anyhow in the view itself and on top of that navigate away from the page.
/// <summary>
/// The sample ViewModel. Should implement INotifyPropertyChanged
/// </summary>
public class MainViewModel
{
private INavigation _navigation;
public MainViewModel(INavigation navigation)
{
_navigation = navigation;
Ducks = new List<Duck>()
{
new Duck() { Name = “George“ },
new Duck() { Name = “Bob“ },
new Duck() { Name = “Sarah“ },
new Duck() { Name = “Clint“ },
};
}
/// <summary>
/// A list of ducks
/// </summary>
/// <value>The ducks.</value>
public List<Duck> Ducks
{
get;
set;
}
public Duck SelectedDuck
{
set
{
if (value != null)
{
// IoC omitted, we should really get someone else to
// create these objects for us.
var viewModel = new DuckViewModel() { Duck = value };
var page = new DuckView(viewModel);
_navigation.PushAsync(page);
}
}
}
}
The other ViewModel (DuckViewModel) simply references the selected duck on the page we navigate to.
public class DuckViewModel
{
public DuckViewModel()
{
}
public Duck Duck
{
get;
set;
}
}
The View
{
public MainView()
{
InitializeComponent();
BindingContext = new MainViewModel(this.Navigation); // Should be injected
// Reset the selected item to make it selectable again
duckListView.ItemSelected += (s, e) => {
duckListView.SelectedItem = null;
};
}
}
<ContentPage xmlns=“http://xamarin.com/schemas/2014/forms“ xmlns:x=“http://schemas.microsoft.com/winfx/2009/xaml“ x:Class=“ListViewNavigation.MainView“>
<ContentPage.Content>
<ListView x:Name=“duckListView“
IsGroupingEnabled=“false“
ItemsSource=“{Binding Ducks}“
HasUnevenRows=“true“
SelectedItem=“{Binding SelectedDuck}“>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Font=“Large“ Text=“{Binding Name}“ />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
Summary
Please give feedback what ever you feel like! And if there’s a better way, I would love for you to enlighten me! 😀