Не могу приостановить раскадровку в WPF - PullRequest
0 голосов
/ 06 февраля 2019

У меня есть ListView, где каждый элемент содержит два изображения, определенных в шаблоне данных.Когда SeViewItem объекта ListView изменяется, я использую триггеры стилей, чтобы начать раскадровки, которые изменяют непрозрачность на двух изображениях SelectedItem в течение общей продолжительности 4 секунды.После того, как раскадровки завершены, я изменяю SelectedItem на следующий элемент из кода, чтобы такие же анимации возникали на следующих двух изображениях и т. Д.

У меня есть кнопка паузы, которая должна приостановить раскадровки, однакоэто не имеет никакого эффекта - анимация раскадровки все еще продолжается.В ходе отладки я подтвердил, что вызывается событие нажатия кнопки «Пауза» и что на раскадровках вызываются методы Pause ().

Вот мой UserControl:

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:ignore="http://www.galasoft.ch/ignore"
    xmlns:viewModel="clr-namespace:WpfTestBase.ViewModel"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
    xmlns:local="clr-namespace:WpfTestBase"
    xmlns:Custom="http://www.galasoft.ch/mvvmlight"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    x:Class="WpfTestBase.View.ListPresentationView"
    mc:Ignorable="d mc"
    d:DesignHeight="480"
    d:DesignWidth="640"
    >
<UserControl.Resources>
    <Storyboard x:Key="PromptStoryboard">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)">
            <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1">
            </DiscreteDoubleKeyFrame>
            <DiscreteDoubleKeyFrame KeyTime="0:0:4" Value="0.25"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
    <Storyboard x:Key="TargetStoryboard">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)">
            <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1">
            </DiscreteDoubleKeyFrame>
            <DiscreteDoubleKeyFrame KeyTime="0:0:2" Value="0.25"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
    <Storyboard x:Key="CombinedStoryboard" Completed="Storyboard_Completed_1">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)">
            <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="1">
            </DiscreteDoubleKeyFrame>
            <DiscreteDoubleKeyFrame KeyTime="0:0:4" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>

    <Style x:Key="CombinedListViewItemStyle" TargetType="{x:Type ListView}">
        <Style.Setters>
            <Setter Property="BorderThickness" Value="0" />
            <Setter Property="FontSize" Value="16" />
            <Setter Property="FontFamily" Value="Arial" />
            <Setter Property="ItemContainerStyle">
                <Setter.Value>
                    <Style TargetType="ListViewItem">
                        <Setter Property="Background" Value="Transparent" />
                        <Setter Property="Opacity" Value="1"/>
                        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type ListViewItem}">
                                    <ContentPresenter />
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                        <Style.Triggers>
                            <Trigger Property="ListViewItem.IsSelected" Value="True">
                                <Trigger.EnterActions>
                                    <BeginStoryboard Storyboard="{StaticResource CombinedStoryboard}">
                                    </BeginStoryboard>
                                </Trigger.EnterActions>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Setter.Value>
            </Setter>
        </Style.Setters>
    </Style>

    <DataTemplate x:Key="CombinedDataTemplate">
        <Grid ShowGridLines="True">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="100"/>
                <ColumnDefinition Width="100"/>
            </Grid.ColumnDefinitions>

            <Image Grid.Column="1" Opacity="0.25" Source="{Binding PromptUriString}">
                <Image.Style>
                    <Style TargetType="{x:Type Image}">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                                                    AncestorType={x:Type ListBoxItem}}, Path=IsSelected}"
                                         Value="True">
                                <DataTrigger.EnterActions>
                                    <BeginStoryboard Storyboard="{StaticResource PromptStoryboard}">
                                    </BeginStoryboard>
                                </DataTrigger.EnterActions>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Image.Style>
            </Image>
            <Image Grid.Column="2" Opacity="0.25" Source="{Binding TargetUriString}">
                <Image.Style>
                    <Style TargetType="{x:Type Image}">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                                                    AncestorType={x:Type ListBoxItem}}, Path=IsSelected}"
                                         Value="True">
                                <DataTrigger.EnterActions>
                                    <BeginStoryboard Storyboard="{StaticResource TargetStoryboard}">
                                    </BeginStoryboard>
                                </DataTrigger.EnterActions>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Image.Style>
            </Image>

        </Grid>
    </DataTemplate>
</UserControl.Resources>

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <Custom:EventToCommand Command="{Binding LoadedCommand}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="50"/>
    </Grid.RowDefinitions>
    <Viewbox Stretch="Fill">
        <ListView x:Name="listViewCombined"
                  ItemsSource="{Binding Qs}" 
                  Margin="0" 
                  BorderBrush="{x:Null}"
                  BorderThickness="4"
                  SelectedItem="{Binding SelectedQ, Mode=TwoWay}"
                  SelectionChanged="listViewCombined_SelectionChanged"
                  ItemTemplate="{DynamicResource CombinedDataTemplate}"
                  Style="{StaticResource CombinedListViewItemStyle}"
                  IsHitTestVisible="False">
        </ListView>
    </Viewbox>
    <StackPanel Orientation="Horizontal"
                Grid.Row="1">
        <Button Name="PauseButton"
                Content="Pause"
                Click="Pause_Click"
                Margin="10"/>
        <Button Name="ResumeButton"
                Content="Resume"
                Click="Resume_Click"
                Margin="10"/>
    </StackPanel>
</Grid>
</UserControl>

И мой кодсзади:

using Microsoft.Win32;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
using WpfTestBase.ViewModel;

namespace WpfTestBase.View
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class ListPresentationView
{
    public ListPresentationView()
    {
        InitializeComponent();
        DataContext = new ListPresentationViewModel();
    }

    private void listViewCombined_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var sbPrompt = FindResource("PromptStoryboard") as Storyboard;
        listViewCombined.BeginStoryboard(sbPrompt);
    }

    private void Storyboard_Completed_1(object sender, EventArgs e)
    {
        var vm = this.DataContext as ListPresentationViewModel;
        vm.CombinedAnimationCompletedCommand.Execute(null);
    }

    private void SuspendOrResumeStoryboard(PowerModes mode)
    {
        if (mode == PowerModes.Resume || mode == PowerModes.Suspend)
        {
            var vm = this.DataContext as ListPresentationViewModel;
            if (vm != null)
            {
                try
                {
                    var sbPrompt = FindResource("PromptStoryboard") as Storyboard;
                    var sbTarget = FindResource("TargetStoryboard") as Storyboard;
                    var sbCombined = FindResource("CombinedStoryboard") as Storyboard;

                    if (sbPrompt != null && sbTarget != null)
                    {
                        if (mode == PowerModes.Suspend)
                        {
                            sbPrompt.Pause();
                            sbTarget.Pause();
                            sbCombined.Pause();

                            Console.WriteLine("===PAUSED" + " " + DateTime.Now.ToString("HH:mm:ss.fff"));
                        }
                        else if (mode == PowerModes.Resume)
                        {
                            sbPrompt.Resume();
                            sbTarget.Resume();
                            sbCombined.Resume();
                            Console.WriteLine("===RESUMED" + " " + DateTime.Now.ToString("HH:mm:ss.fff"));
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }
    }

    private void Pause_Click(object sender, RoutedEventArgs e)
    {
        SuspendOrResumeStoryboard(PowerModes.Suspend);
    }

    private void Resume_Click(object sender, RoutedEventArgs e)
    {
        SuspendOrResumeStoryboard(PowerModes.Resume);
    }
}
}

И моя ViewModel:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using WpfTestBase.Model;

namespace WpfTestBase.ViewModel
{
/// <summary>
/// This class contains properties that a View can data bind to.
/// <para>
/// See http://www.galasoft.ch/mvvm
/// </para>
/// </summary>
public class ListPresentationViewModel : ViewModelBase
{
    private int _currCombinedIndex = 0;
    public List<WmtQ> Qs { get; set; }

    /// <summary>
    /// The <see cref="SelectedQ" /> property's name.
    /// </summary>
    public const string SelectedQPropertyName = "SelectedQ";

    private WmtQ _selectedQ = null;

    /// <summary>
    /// Sets and gets the SelectedQ property.
    /// Changes to that property's value raise the PropertyChanged event. 
    /// </summary>
    public WmtQ SelectedQ
    {
        get
        {
            return _selectedQ;
        }
        set
        {
            Set(() => SelectedQ, ref _selectedQ, value);

        }
    }

    private RelayCommand _loadedCommand;

    /// <summary>
    /// Gets the LoadedCommand.
    /// </summary>
    public RelayCommand LoadedCommand
    {
        get
        {
            return _loadedCommand
                ?? (_loadedCommand = new RelayCommand(
                () =>
                {
                    SelectedQ = Qs[_currCombinedIndex];
                }));
        }
    }

    private RelayCommand _combinedAnimationCompletedCommand;

    /// <summary>
    /// Gets the CombinedAnimationCompletedCommand.
    /// </summary>
    public RelayCommand CombinedAnimationCompletedCommand
    {
        get
        {
            return _combinedAnimationCompletedCommand
                ?? (_combinedAnimationCompletedCommand = new RelayCommand(
                () =>
                {
                    _currCombinedIndex++;
                    if (_currCombinedIndex < Qs.Count)
                    {
                        SelectedQ = Qs[_currCombinedIndex];
                    }
                }));
        }
    }

    /// <summary>
    /// Initializes a new instance of the ListPresentationViewModel class.
    /// </summary>
    public ListPresentationViewModel()
    {
        Qs = new List<WmtQ>();
        var qList = new List<WmtQ> { new WmtQ("One", "A"), new WmtQ("Two", "B"), new WmtQ("Three", "C"), new WmtQ("Four", "D") };
        Qs = qList;
    }

}
}

Ответы [ 2 ]

0 голосов
/ 08 февраля 2019

Благодаря ответу redcurry и комментарию mm8 я решил эту проблему, переместив раскадровки целиком в code-behind.

Мой UserControl:

<UserControl.Resources>
    <Style x:Key="CombinedListViewItemStyle" TargetType="{x:Type ListView}">
        <Style.Setters>
            <Setter Property="BorderThickness" Value="0" />
            <Setter Property="FontSize" Value="16" />
            <Setter Property="FontFamily" Value="Arial" />
            <Setter Property="ItemContainerStyle">
                <Setter.Value>
                    <Style TargetType="ListViewItem">
                        <Setter Property="Background" Value="Transparent" />
                        <Setter Property="Opacity" Value="1"/>
                        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="{x:Type ListViewItem}">
                                    <ContentPresenter />
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </Setter.Value>
            </Setter>
        </Style.Setters>
    </Style>

    <DataTemplate x:Key="CombinedDataTemplate">
        <Grid ShowGridLines="True">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="50"/>
                <ColumnDefinition Width="100"/>
                <ColumnDefinition Width="100"/>
            </Grid.ColumnDefinitions>
            <Image Grid.Column="1" Opacity="0.25" Source="{Binding PromptUriString}"
                   Name="PromptImage">
            </Image>
            <Image Grid.Column="2" Opacity="0.25" Source="{Binding TargetUriString}"
                   Name="TargetImage">
            </Image>
        </Grid>
    </DataTemplate>
</UserControl.Resources>

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <Custom:EventToCommand Command="{Binding LoadedCommand}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="50"/>
    </Grid.RowDefinitions>
    <Viewbox Stretch="Fill">
        <ListView x:Name="listViewCombined"
                  ItemsSource="{Binding Qs}" 
                  Margin="0" 
                  BorderBrush="{x:Null}"
                  BorderThickness="4"
                  SelectedItem="{Binding SelectedQ, Mode=TwoWay}"
                  SelectionChanged="listViewCombined_SelectionChanged"
                  ItemTemplate="{DynamicResource CombinedDataTemplate}"
                  Style="{StaticResource CombinedListViewItemStyle}"
                  IsHitTestVisible="False"
                  IsSynchronizedWithCurrentItem="True">
        </ListView>
    </Viewbox>
    <StackPanel Orientation="Horizontal"
                Grid.Row="1">
        <Button Name="PauseButton"
                Content="Pause"
                Click="Pause_Click"
                Margin="10"/>
        <Button Name="ResumeButton"
                Content="Resume"
                Click="Resume_Click"
                Margin="10"/>
    </StackPanel>
</Grid>

Code-Behind:

public partial class ListPresentationView
{
    Storyboard sbPrompt;
    Storyboard sbTarget;

    bool _isCompSleep = false;

    List<int> _completedListViewIndices = new List<int>();

    public ListPresentationView()
    {
        InitializeComponent();
        DataContext = new ListPresentationViewModel();

        // Needed for controlling storyboards
        NameScope.SetNameScope(this, new NameScope());
    }

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        RegisterElementsInNameScope();
        AnimateCurrentItem(0);
    }

    private void RegisterElementsInNameScope()
    {
        var gen = listViewCombined.ItemContainerGenerator;
        var obj = (gen.ContainerFromItem(listViewCombined.Items[0]));
        if (obj != null)
        {
            ListViewItem myListBoxItem = (ListViewItem)obj;

            ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(myListBoxItem);

            DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
            Image promptImage = (Image)myDataTemplate.FindName("PromptImage", myContentPresenter);
            Image targetImage = (Image)myDataTemplate.FindName("TargetImage", myContentPresenter);

            this.RegisterName(listViewCombined.Name, listViewCombined);
            this.RegisterName(promptImage.Name, promptImage);
            this.RegisterName(targetImage.Name, targetImage);
        }
    }

    private void AnimateCurrentItem(int currIndex)
    {
        Console.WriteLine("AnimateCurreintItem, currIndex: " + currIndex);
        var gen = listViewCombined.ItemContainerGenerator;
        var obj = (gen.ContainerFromItem(listViewCombined.Items[currIndex]));
        if (obj != null)
        {
            ListViewItem myListBoxItem = (ListViewItem)obj;

            ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(myListBoxItem);

            DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
            Image promptImage = (Image)myDataTemplate.FindName("PromptImage", myContentPresenter);
            Image targetImage = (Image)myDataTemplate.FindName("TargetImage", myContentPresenter);

            this.UnregisterName(listViewCombined.Name);
            this.UnregisterName(promptImage.Name);
            this.UnregisterName(targetImage.Name);
            this.RegisterName(listViewCombined.Name, listViewCombined);
            this.RegisterName(promptImage.Name, promptImage);
            this.RegisterName(targetImage.Name, targetImage);

            DoubleAnimation promptAni = new DoubleAnimation();
            promptAni.From = 1;
            promptAni.To = 0;
            promptAni.Duration = new Duration(TimeSpan.FromMilliseconds(4000));

            sbPrompt = new Storyboard();
            sbPrompt.Children.Add(promptAni);
            Storyboard.SetTargetName(promptAni, promptImage.Name);
            Storyboard.SetTargetProperty(promptAni, new PropertyPath(Image.OpacityProperty));

            DoubleAnimationUsingKeyFrames targetAni = new DoubleAnimationUsingKeyFrames();
            var kf1 = new DiscreteDoubleKeyFrame(0.25, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0)));
            var kf2 = new DiscreteDoubleKeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(2)));
            var kf3 = new DiscreteDoubleKeyFrame(0.25, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(4)));
            targetAni.KeyFrames.Add(kf1);
            targetAni.KeyFrames.Add(kf2);
            targetAni.KeyFrames.Add(kf3);

            if (sbTarget != null) sbTarget.Completed -= Storyboard_Completed_1;
            sbTarget = new Storyboard();
            sbTarget.Completed += Storyboard_Completed_1;

            sbTarget.Children.Add(targetAni);
            Storyboard.SetTargetName(targetAni, targetImage.Name);
            Storyboard.SetTargetProperty(targetAni, new PropertyPath(Image.OpacityProperty));

            sbPrompt.Begin(this, true);
            sbTarget.Begin(this, true);

        }
        else
        {
            ;
        }
    }

    private void listViewCombined_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var lView = sender as ListView;
        if (lView != null)
        {
            var index = lView.SelectedIndex;
            if (index >= 0 && index < lView.Items.Count)
            {
                AnimateCurrentItem(index);
            }
        }
    }

    private childItem FindVisualChild<childItem>(DependencyObject obj)
        where childItem : DependencyObject
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(obj, i);
            if (child != null && child is childItem)
                return (childItem)child;
            else
            {
                childItem childOfChild = FindVisualChild<childItem>(child);
                if (childOfChild != null)
                    return childOfChild;
            }
        }
        return null;
    }

    private void Storyboard_Completed_1(object sender, EventArgs e)
    {
        Console.WriteLine("Storyboard_Completed_1 " + DateTime.Now.ToString("HH:mm:ss.fff"));
        var vm = this.DataContext as ListPresentationViewModel;

        if (!_isCompSleep) vm.CombinedAnimationCompletedCommand.Execute(null);
    }

    private void SuspendOrResumeStoryboard(PowerModes mode)
    {
        if (mode == PowerModes.Resume || mode == PowerModes.Suspend)
        {
            {
                try
                {
                    if (sbPrompt != null && sbTarget != null)
                    {
                        if (mode == PowerModes.Suspend)
                        {
                            _isCompSleep = true;
                            sbPrompt.Pause(this);
                            sbTarget.Pause(this);
                            Console.WriteLine("===PAUSED" + " " + DateTime.Now.ToString("HH:mm:ss.fff"));
                        }
                        else if (mode == PowerModes.Resume)
                        {
                            _isCompSleep = false;
                            sbPrompt.Resume(this);
                            sbTarget.Resume(this);
                            Console.WriteLine("===RESUMED" + " " + DateTime.Now.ToString("HH:mm:ss.fff"));
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }
    }

    private void Pause_Click(object sender, RoutedEventArgs e)
    {
        SuspendOrResumeStoryboard(PowerModes.Suspend);
    }

    private void Resume_Click(object sender, RoutedEventArgs e)
    {
        SuspendOrResumeStoryboard(PowerModes.Resume);
    }
}

И View-модель:

public class ListPresentationViewModel : ViewModelBase
{
    private int _currCombinedIndex = 0;
    public List<WmtQ> Qs { get; set; }

    /// <summary>
    /// The <see cref="SelectedQ" /> property's name.
    /// </summary>
    public const string SelectedQPropertyName = "SelectedQ";

    private WmtQ _selectedQ = null;

    /// <summary>
    /// Sets and gets the SelectedQ property.
    /// Changes to that property's value raise the PropertyChanged event. 
    /// </summary>
    public WmtQ SelectedQ
    {
        get
        {
            return _selectedQ;
        }
        set
        {
            Set(() => SelectedQ, ref _selectedQ, value);

        }
    }

    private RelayCommand _loadedCommand;

    /// <summary>
    /// Gets the LoadedCommand.
    /// </summary>
    public RelayCommand LoadedCommand
    {
        get
        {
            return _loadedCommand
                ?? (_loadedCommand = new RelayCommand(
                () =>
                {
                    SelectedQ = Qs[_currCombinedIndex];
                }));
        }
    }

    private RelayCommand _combinedAnimationCompletedCommand;

    /// <summary>
    /// Gets the CombinedAnimationCompletedCommand.
    /// </summary>
    public RelayCommand CombinedAnimationCompletedCommand
    {
        get
        {
            return _combinedAnimationCompletedCommand
                ?? (_combinedAnimationCompletedCommand = new RelayCommand(
                () =>
                {
                    _currCombinedIndex++;
                    if (_currCombinedIndex < Qs.Count)
                    {
                        SelectedQ = Qs[_currCombinedIndex];
                    }
                }));
        }
    }

    /// <summary>
    /// Initializes a new instance of the ListPresentationViewModel class.
    /// </summary>
    public ListPresentationViewModel()
    {
        Qs = new List<WmtQ>();
        var qList = new List<WmtQ> { new WmtQ("One", "A"), new WmtQ("Two", "B"), new WmtQ("Three", "C"), new WmtQ("Four", "D") };
        Qs = qList;
    }

}
0 голосов
/ 07 февраля 2019

Согласно документации , похоже, что тегу BeginStoryboard необходимо иметь имя, чтобы его можно было контролировать:

Если вы дадите BeginStoryboardname, указав его свойство Name, вы делаете его управляемой раскадровкой.После этого вы можете в интерактивном режиме управлять раскадровкой после ее запуска.

Однако я также читал, что у некоторых людей (см. Ответ Кристины Л.) возникли проблемы с раскадровками, когдасмешивание триггеров XAML и кодаЕсли вы не можете заставить его работать, может быть лучше использовать только триггеры или только код, но не оба.

Если вы собираетесь использовать код позади, раскадровка должна бытьстало управляемым путем указания true для параметра IsControllable при вызове метода Begin для Storyboard.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...