Реализация OnBoarding процесса в приложении Xamarin.iOS
Требование
Недавно мне пришлось реализовывать процесс адаптации в iOS приложении JustGiving, который состоит из серии экранов, по которым можно преходить вперед и назад.
Решение
Я использовал UIPageViewController
с UIPageViewControllerDataSource
для навигации шагов и UIPageControl
для отображения процесса.
Результат
Шаги
Каждый шаг это UIViewController
, который реализует следующий интерфейс.
public interface IMultiStepProcessStep : IDisposable
{
int StepIndex { get; set; }
event EventHandler<MultiStepProcessStepEventArgs> StepActivated;
event EventHandler<MultiStepProcessStepEventArgs> StepDeactivated;
}
public class MultiStepProcessStepEventArgs
{
public int Index { get; set; }
}
Шаг UIViewController
Шаг публикует свой индекс при активации или деактивации, как активный шаг. Это делают методы ViewDidAppear
и ViewWillDisappear
.
public class MakeGoodthingsHappenStep : UIViewController, IMultiStepProcessStep
{
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
StepActivated?.Invoke(this, new MultiStepProcessStepEventArgs { Index = StepIndex });
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
StepDeactivated?.Invoke(this, new MultiStepProcessStepEventArgs { Index = StepIndex });
}
public int StepIndex { get; set; }
public event EventHandler<MultiStepProcessStepEventArgs> StepActivated;
public event EventHandler<MultiStepProcessStepEventArgs> StepDeactivated;
}
UIPageViewControllerDataSource
Источник данных представляет собой UIPageViewControllerDataSource
, который строится из списка шагов IMultiStepProcessStep
.
public class MultiStepProcessDataSource : UIPageViewControllerDataSource
{
private readonly List<IMultiStepProcessStep> _steps;
public MultiStepProcessDataSource(List<IMultiStepProcessStep> steps)
{
if (steps == null)
{
throw new ArgumentNullException(nameof(steps));
}
if (!steps.Any())
{
throw new ArgumentException("steps cannot be empty.", nameof(steps));
}
if (steps.Any(s => !(s is UIViewController)))
{
throw new ArgumentException("all steps must be a UIViewController", nameof(steps));
}
_steps = steps;
for (int i = 0; i < _steps.Count; i++)
{
var step = _steps[i];
step.StepIndex = i;
}
}
public List<IMultiStepProcessStep> Steps => _steps;
public override UIViewController GetPreviousViewController(UIPageViewController pageViewController,
UIViewController referenceViewController)
{
var step = referenceViewController as IMultiStepProcessStep;
if (step == null)
{
return null;
}
var index = _steps.IndexOf(step);
if (index <= 0)
{
return null;
}
return _steps[index - 1] as UIViewController;
}
public override UIViewController GetNextViewController(UIPageViewController pageViewController,
UIViewController referenceViewController)
{
var step = referenceViewController as IMultiStepProcessStep;
if (step == null)
{
return null;
}
var index = _steps.IndexOf(step);
if (index + 1 == _steps.Count)
{
return null;
}
return _steps[(step.StepIndex + 1)] as UIViewController;
}
}
UIPageViewController
UIPageViewController
построен из источника данных.
public sealed class MultiStepProcessHorizontal : UIPageViewController
{
public MultiStepProcessHorizontal(MultiStepProcessDataSource dataSource)
:base(UIPageViewControllerTransitionStyle.Scroll,
UIPageViewControllerNavigationOrientation.Horizontal)
{
DataSource = dataSource;
SetViewControllers(new[] {dataSource.Steps.FirstOrDefault() as UIViewController},
UIPageViewControllerNavigationDirection.Forward,
false,
null);
}
}
UIPageControl
UIPageControl
используется, чтобы указать, какой шаг активен.
Собираем все вместе в OnBoardingViewController
Обработчики событий для случаев, когда шаг активируется и деактивируется используются, чтобы установить индекс текущей страницы и обновить любые другие части пользовательского интерфейса если это будет нужно.
private void HandleStepActivated(object sender, MultiStepProcessStepEventArgs args)
{
_pageControl.CurrentPage = args.Index;
}
private void HandleStepDeactivated(object sender, MultiStepProcessStepEventArgs args)
{
//update the UI as required while transitioning between steps
}
Получаем все шаги, которые будут являться частью процесса адаптации и навешиваем обравотчики на события StepActivated
и StepDeactivated
.
private List<IMultiStepProcessStep> GetSteps()
{
var steps = new List<IMultiStepProcessStep>()
{
new MakeGoodthingsHappenStep(),
new FundraiseStep(),
new ConnectStep(),
new DiscoverStep(),
new GetStartedStep()
};
steps.ForEach(s =>
{
s.StepActivated += HandleStepActivated;
s.StepDeactivated += HandleStepDeactivated;
});
return steps;
}
Настраиваем и добавляем элементы управления UIPageViewController
и UIPageControl
к основному представлению.
private MultiStepProcessHorizontal _pageViewController;
private UIPageControl _pageControl;
private List<IMultiStepProcessStep> _steps;
public List<IMultiStepProcessStep> Steps => _steps ?? (_steps = GetSteps());
public override void LoadView()
{
View = new UIView();
_pageViewController = new MultiStepProcessHorizontal(new MultiStepProcessDataSource(Steps));
_pageControl = new UIPageControl
{
CurrentPage = 0,
Pages = Steps.Count
};
View.Add(_pageViewController.View);
View.Add(_pageControl);
}
На этом все!