Xamarin.ios의 컬렉션 뷰Collection Views in Xamarin.iOS

컬렉션 뷰를 사용 하면 임의의 레이아웃을 사용 하 여 콘텐츠를 표시할 수 있습니다. 또한 사용자 지정 레이아웃을 지 원하는 동시에 쉽게 표 형태의 레이아웃을 만들 수 있습니다.Collection Views allow content to be displayed using arbitrary layouts. They allow easily creating grid-like layouts out of the box, while also supporting custom layouts.

UICollectionView 클래스에서 제공 되는 컬렉션 뷰는 레이아웃을 사용 하 여 화면에 여러 항목을 표시 하는 iOS 6의 새로운 개념입니다.Collection Views, available in the UICollectionView class, are a new concept in iOS 6 that introduce presenting multiple items on the screen using layouts. 항목을 만들고 해당 항목과 상호 작용 하는 UICollectionView에 데이터를 제공 하는 패턴은 iOS 개발에 일반적으로 사용 되는 것과 동일한 위임 및 데이터 소스 패턴을 따릅니다.The patterns for providing data to a UICollectionView to create items and interact with those items follow the same delegation and data source patterns commonly used in iOS development.

그러나 컬렉션 뷰는 UICollectionView 자체와 독립적인 레이아웃 하위 시스템에서 작동 합니다.However, Collection Views work with a layout subsystem that is independent of the UICollectionView itself. 따라서 다른 레이아웃을 제공 하기만 하면 쉽게 컬렉션 뷰의 표시를 변경할 수 있습니다.Therefore, simply providing a different layout can easily change the presentation of a Collection View.

iOS는 추가 작업 없이 그리드와 같은 줄 기반 레이아웃을 만들 수 있도록 하는 UICollectionViewFlowLayout 이라는 레이아웃 클래스를 제공 합니다.iOS provides a layout class called UICollectionViewFlowLayout that allows line-based layouts such as a grid to be created with no additional work. 또한 사용자 지정 레이아웃을 만들어 가정할 수 있는 모든 프레젠테이션을 수행할 수도 있습니다.Also, custom layouts can also be created that allow any presentation you can imagine.

UICollectionView 기본 사항UICollectionView Basics

UICollectionView 클래스는 세 가지 항목으로 구성 되어 있습니다.The UICollectionView class is made up of three different items:

  • – 각 항목에 대 한 데이터 기반 뷰Cells – Data-driven views for each item
  • 보조 뷰 – 섹션과 연결 된 데이터 기반 뷰입니다.Supplementary Views – Data-driven views associated with a section.
  • 장식 뷰 – 레이아웃으로 만든 비 데이터 기반 뷰Decoration Views – Non-data driven views created by a layout

Cells

셀은 컬렉션 뷰에 표시 되는 데이터 집합의 단일 항목을 나타내는 개체입니다.Cells are objects that represent a single item in the data set that is being presented by the collection view. 각 셀은 아래 그림에 표시 된 것 처럼 세 가지 뷰로 구성 된 UICollectionViewCell 클래스의 인스턴스입니다.Each cell is an instance of the UICollectionViewCell class, which is composed of three different views, as shown in the figure below:

UICollectionViewCell 클래스에는 이러한 각 뷰에 대해 다음과 같은 속성이 있습니다.The UICollectionViewCell class has the following properties for each of these views:

  • ContentView –이 보기에는 셀에 표시 되는 내용이 포함 됩니다.ContentView – This view contains the content that the cell presents. 화면의 맨 위 z 순서에서 렌더링 됩니다.It is rendered in the topmost z-order on the screen.
  • SelectedBackgroundView – 셀에는 기본적으로 선택이 지원 됩니다.SelectedBackgroundView – Cells have built in support for selection. 이 보기는 셀이 선택 되어 있음을 시각적으로 나타내는 데 사용 됩니다.This view is used to visually denote that a cell is selected. 셀이 선택 될 때 ContentView 바로 아래에 렌더링 됩니다.It is rendered just below the ContentView when a cell is selected.
  • BackgroundViewBackgroundView에서 제공 하는 배경을 표시할 수도 있습니다.BackgroundView – Cells can also display a background, which is presented by the BackgroundView . 이 보기는 SelectedBackgroundView 아래에 렌더링 됩니다.This view is rendered beneath the SelectedBackgroundView .

ContentView를 설정 하 여 BackgroundViewSelectedBackgroundView보다 작게 하는 경우 BackgroundView를 사용 하 여 콘텐츠를 시각적으로 프레임으로 표시할 수 있습니다 .이 경우에는 아래와 같이 셀이 선택 될 때 SelectedBackgroundView 표시 됩니다. :By setting the ContentView such that it is smaller than the BackgroundView and SelectedBackgroundView, the BackgroundView can be used to visually frame the content, while the SelectedBackgroundView will be displayed when a cell is selected, as shown below:

위의 스크린샷에 있는 셀은 다음 코드에 표시 된 대로 UICollectionViewCell에서 상속 하 고 ContentView, SelectedBackgroundViewBackgroundView 속성을 각각 설정 하 여 만듭니다.The Cells in the screenshot above are created by inheriting from UICollectionViewCell and setting the ContentView, SelectedBackgroundView and BackgroundView properties, respectively, as shown in the following code:

public class AnimalCell : UICollectionViewCell
{
        UIImageView imageView;

        [Export ("initWithFrame:")]
        public AnimalCell (CGRect frame) : base (frame)
        {
            BackgroundView = new UIView{BackgroundColor = UIColor.Orange};

            SelectedBackgroundView = new UIView{BackgroundColor = UIColor.Green};

            ContentView.Layer.BorderColor = UIColor.LightGray.CGColor;
            ContentView.Layer.BorderWidth = 2.0f;
            ContentView.BackgroundColor = UIColor.White;
            ContentView.Transform = CGAffineTransform.MakeScale (0.8f, 0.8f);

            imageView = new UIImageView (UIImage.FromBundle ("placeholder.png"));
            imageView.Center = ContentView.Center;
            imageView.Transform = CGAffineTransform.MakeScale (0.7f, 0.7f);

            ContentView.AddSubview (imageView);
        }

        public UIImage Image {
            set {
                imageView.Image = value;
            }
        }
}

보조 뷰Supplementary Views

보조 보기는 UICollectionView의 각 섹션과 관련 된 정보를 표시 하는 보기입니다.Supplementary Views are views that present information associated with each section of a UICollectionView. 셀과 마찬가지로 보조 뷰도 데이터를 기반으로 합니다.Like Cells, Supplementary Views are data-driven. 셀에서 데이터 원본의 항목 데이터를 제공 하는 경우 보충 보기는 bookshelf의 책 범주 또는 음악 라이브러리의 음악 장르와 같은 섹션 데이터를 제공 합니다.Where Cells present the item data from a data source, Supplementary Views present the section data, such as the categories of book in a bookshelf or the genre of music in a music library.

예를 들어 아래 그림에 표시 된 것 처럼 보조 뷰를 사용 하 여 특정 섹션에 대 한 머리글을 표시할 수 있습니다.For example, a Supplementary View could be used to present a header for a particular section, as shown in the figure below:

보조 뷰를 사용 하려면 먼저 ViewDidLoad 메서드에 등록 해야 합니다.To use a Supplementary View, it first needs to be registered in the ViewDidLoad method:

CollectionView.RegisterClassForSupplementaryView (typeof(Header), UICollectionElementKindSection.Header, headerId);

그런 다음 DequeueReusableSupplementaryView를 사용 하 여 만든 GetViewForSupplementaryElement를 사용 하 여 뷰를 반환 해야 하 고 UICollectionReusableView에서 상속 합니다.Then, the view needs to be returned by using GetViewForSupplementaryElement, created by using DequeueReusableSupplementaryView, and inherits from UICollectionReusableView. 다음 코드 조각에서는 위의 스크린샷에 표시 된 SupplementaryView을 생성 합니다.The following code snippet will produce the SupplementaryView shown in the screenshot above:

public override UICollectionReusableView GetViewForSupplementaryElement (UICollectionView collectionView, NSString elementKind, NSIndexPath indexPath)
        {
            var headerView = (Header)collectionView.DequeueReusableSupplementaryView (elementKind, headerId, indexPath);
            headerView.Text = "Supplementary View";
            return headerView;
        }

보조 보기는 머리글 및 바닥글 보다 더 일반적입니다.Supplementary Views are more generic than just headers and footers. 컬렉션 보기의 어느 위치에 나 배치 될 수 있으며 모든 뷰로 구성 하 여 모양을 완벽 하 게 사용자 지정할 수 있습니다.They can be positioned anywhere in the collection view and can be comprised of any views, making their appearance fully customizable.

장식 뷰Decoration Views

데코레이션 보기는 UICollectionView에 표시 될 수 있는 전적으로 시각적 뷰입니다.Decoration Views are purely visual views that can be displayed in a UICollectionView. 셀 및 보충 뷰와 달리 데이터를 기반으로 하지 않습니다.Unlike Cells and Supplementary Views, they are not data-driven. 이러한 항목은 항상 레이아웃의 하위 클래스 내에 생성 되며, 이후에 콘텐츠의 레이아웃으로 변경 될 수 있습니다.They are always created within a layout's subclass and subsequently can change as the content’s layout. 예를 들어 다음과 같이 데코레이션 뷰를 사용 하 여 UICollectionView내용으로 스크롤 하는 배경 뷰를 표시할 수 있습니다.For example, a Decoration View could be used to present a background view that scrolls with the content in the UICollectionView, as shown below:

아래 코드 조각은 samples CircleLayout 클래스에서 배경을 빨강으로 변경 합니다.The code snippet below changes the background to red in the samples CircleLayout class:

public class MyDecorationView : UICollectionReusableView
 {
   [Export ("initWithFrame:")]
   public MyDecorationView (CGRect frame) : base (frame)
   {
     BackgroundColor = UIColor.Red;
   }
 }

데이터 소스Data Source

UITableViewMKMapView와 같이 iOS의 다른 부분과 마찬가지로 UICollectionView UICollectionViewDataSource 클래스를 통해 xamarin.ios에 노출 되는 데이터 원본에서 데이터를 가져옵니다.As with other parts of iOS, such as UITableView and MKMapView, UICollectionView gets its data from a data source, which is exposed in Xamarin.iOS via the UICollectionViewDataSource class. 이 클래스는 다음과 같은 UICollectionView에 콘텐츠를 제공 합니다.This class is responsible for providing content to the UICollectionView such as:

  • CellsGetCell 메서드에서 반환 됩니다.Cells – Returned from GetCell method.
  • 보조 뷰 -GetViewForSupplementaryElement 메서드에서 반환 됩니다.Supplementary Views – Returned from GetViewForSupplementaryElement method.
  • 섹션 수NumberOfSections 메서드에서 반환 됩니다.Number of sections – Returned from NumberOfSections method. 구현 되지 않은 경우 기본값은 1입니다.Defaults to 1 if not implemented.
  • 섹션 당 항목 수GetItemsCount 메서드에서 반환 됩니다.Number of items per section – Returned from GetItemsCount method.

UICollectionViewControllerUICollectionViewController

편의상 UICollectionViewController 클래스를 사용할 수 있습니다. 이는 다음 섹션에서 설명 하는 대리자와 해당 UICollectionView 뷰의 데이터 원본으로 자동으로 구성 됩니다.For convenience, the UICollectionViewController class is available.This is automatically configured to be both the delegate, which is discussed in the next section, and data source for its UICollectionView view.

UITableView와 마찬가지로 UICollectionView 클래스는 해당 데이터 소스를 호출 하 여 화면에 있는 항목에 대 한 셀을 가져옵니다.As with UITableView, the UICollectionView class will only call its data source to get Cells for items that are on the screen. 화면에서 스크롤 하는 셀은 다음 이미지에 나와 있는 것 처럼 다시 사용 하기 위해 큐에 배치 됩니다.Cells that scroll off the screen are placed in to a queue for reuse, as the following image illustrates:

UICollectionViewUITableView를 사용 하 여 셀 재사용을 간소화 했습니다.Cell reuse has been simplified with UICollectionView and UITableView. 셀이 시스템에 등록 되 면 다시 사용 큐에서 사용할 수 없는 경우 더 이상 데이터 원본에서 직접 셀을 만들 필요가 없습니다.You no longer need to create a Cell directly in the data source if one isn’t available in the reuse queue, as Cells are registered with the system. 다시 사용 큐에서 셀의 큐에서 제거를 호출할 때 셀을 사용할 수 없는 경우 iOS는 등록 된 유형 또는 nib을 기반으로 자동으로 만듭니다.If a Cell is not available when making the call to de-queue the Cell from the reuse queue, iOS will create it automatically based upon the type or nib that was registered. 이와 동일한 기법은 보충 보기 에서도 사용할 수 있습니다.The same technique is also available for Supplementary Views.

예를 들어 AnimalCell 클래스를 등록 하는 다음 코드를 살펴보겠습니다.For example, consider the following code which registers the AnimalCell class:

static NSString animalCellId = new NSString ("AnimalCell");
CollectionView.RegisterClassForCell (typeof(AnimalCell), animalCellId);

항목이 화면에 있기 때문에 셀이 필요한 UICollectionView UICollectionView는 해당 데이터 소스의 GetCell 메서드를 호출 합니다.When a UICollectionView needs a cell because its item is on the screen, the UICollectionView calls its data source’s GetCell method. UITableView에서 작동 하는 방식과 유사 하 게이 메서드는 지원 데이터에서 셀을 구성 하는 것과 유사 하며,이 경우에는 AnimalCell 클래스입니다.Similar to how this works with UITableView, this method is responsible for configuring a Cell from the backing data, which would be an AnimalCell class in this case.

다음 코드는 AnimalCell 인스턴스를 반환 하는 GetCell의 구현을 보여 줍니다.The following code shows an implementation of GetCell that returns an AnimalCell instance:

public override UICollectionViewCell GetCell (UICollectionView collectionView, Foundation.NSIndexPath indexPath)
{
        var animalCell = (AnimalCell)collectionView.DequeueReusableCell (animalCellId, indexPath);

        var animal = animals [indexPath.Row];

        animalCell.Image = animal.Image;

        return animalCell;
}

DequeReusableCell에 대 한 호출은 다시 사용 큐에서 셀이 큐에서 제거 되거나 CollectionView.RegisterClassForCell호출에 등록 된 형식에 따라 생성 된 큐에서 셀을 사용할 수 없는 경우입니다.The call to DequeReusableCell is where the cell will be either de-queued from the reuse queue or, if a cell is not available in the queue, created based upon the type registered in the call to CollectionView.RegisterClassForCell.

이 경우 AnimalCell 클래스를 등록 하면 iOS가 새 AnimalCell를 만든 다음, 셀을 큐에서 제거 하는 호출이 수행 될 때이를 반환 하 고, 그 후에는 동물 클래스에 포함 된 이미지를 사용 하 여 구성 되 고 UICollectionView에 표시 하기 위해 반환 됩니다.In this case, by registering the AnimalCell class, iOS will create a new AnimalCell internally and return it when a call to de-queue a cell is made, after which it is configured with the image contained in the animal class and returned for display to the UICollectionView.

대리자(delegate)Delegate

UICollectionView 클래스는 UICollectionViewDelegate 형식의 대리자를 사용 하 여 UICollectionView의 콘텐츠와의 상호 작용을 지원 합니다.The UICollectionView class uses a delegate of type UICollectionViewDelegate to support interaction with content in the UICollectionView. 이렇게 하면 다음을 제어할 수 있습니다.This allows control of:

  • 셀 선택 – 셀이 선택 되어 있는지 여부를 결정 합니다.Cell Selection – Determines if a cell is selected.
  • 셀 강조 표시 – 셀이 현재 작업 중인지 확인 합니다.Cell Highlighting – Determines if a cell is currently being touched.
  • 셀 메뉴 – 긴 누름 제스처에 대 한 응답으로 셀에 대해 표시 되는 메뉴입니다.Cell Menus – Menu displayed for a cell in response to a long press gesture.

데이터 소스와 마찬가지로 UICollectionViewController는 기본적으로 UICollectionView에 대 한 대리자로 구성 됩니다.As with the data source, the UICollectionViewController is configured by default to be the delegate for the UICollectionView.

셀 강조 표시Cell HighLighting

셀을 누르면 셀이 강조 표시 된 상태로 전환 되 고 사용자가 셀에서 손가락을 뗄 때까지 선택 되지 않습니다.When a Cell is pressed, the cell transitions into a highlighted state, and it is not selected until the user lifts their finger from the Cell. 이렇게 하면 실제로 선택 되기 전에 셀의 모양을 일시적으로 변경할 수 있습니다.This allows a temporary change in the appearance of the cell before it is actually selected. 선택 시 셀의 SelectedBackgroundView 표시 됩니다.Upon selection, the Cell’s SelectedBackgroundView is displayed. 아래 그림에서는 선택이 발생 하기 직전에 강조 표시 된 상태를 보여 줍니다.The figure below shows the highlighted state just before the selection occurs:

강조 표시를 구현 하기 위해 UICollectionViewDelegateItemHighlightedItemUnhighlighted 메서드를 사용할 수 있습니다.To implement highlighting, the ItemHighlighted and ItemUnhighlighted methods of the UICollectionViewDelegate can be used. 예를 들어 다음 코드는 위 이미지에 표시 된 것 처럼 셀이 강조 표시 되 고 강조 표시 되지 않은 경우 흰색 배경으로 ContentView의 노란색 배경을 적용 합니다.For example, the following code will apply a yellow background of the ContentView when the Cell is highlighted, and a white background when un-highlighted, as shown in the image above:

public override void ItemHighlighted (UICollectionView collectionView, NSIndexPath indexPath)
{
        var cell = collectionView.CellForItem(indexPath);
        cell.ContentView.BackgroundColor = UIColor.Yellow;
}

public override void ItemUnhighlighted (UICollectionView collectionView, NSIndexPath indexPath)
{
        var cell = collectionView.CellForItem(indexPath);
        cell.ContentView.BackgroundColor = UIColor.White;
}

선택 해제Disabling Selection

선택은 기본적으로 UICollectionView에서 사용 하도록 설정 됩니다.Selection is enabled by default in UICollectionView. 선택을 사용 하지 않도록 설정 하려면 ShouldHighlightItem를 재정의 하 고 아래와 같이 false를 반환 합니다.To disable selection, override ShouldHighlightItem and return false as shown below:

public override bool ShouldHighlightItem (UICollectionView collectionView, NSIndexPath indexPath)
{
        return false;
}

강조 표시를 사용 하지 않도록 설정 하면 셀을 선택 하는 프로세스도 사용 하지 않도록 설정 됩니다.When highlighting is disabled, the process of selecting a cell is disabled as well. 또한 ShouldHighlightItem를 구현 하 고 false를 반환 하는 경우 ShouldSelectItem가 호출 되지 않더라도 선택을 직접 제어 하는 ShouldSelectItem 메서드도 있습니다.Additionally, there is also a ShouldSelectItem method that controls selection directly, although if ShouldHighlightItem is implemented and returns false, ShouldSelectItem is not called.

ShouldSelectItem를 사용 하면 ShouldHighlightItem 구현 되지 않은 경우 항목 단위로 항목을 설정 하거나 해제할 수 있습니다.ShouldSelectItem allows selection to be turned on or off on an item-by-item basis, when ShouldHighlightItem is not implemented. 또한 ShouldHighlightItem 구현 되 고 true를 반환 하는 동안 ShouldSelectItem가 false를 반환 하는 경우 선택 없이 강조 표시를 허용 합니다.It also allows highlighting without selection, if ShouldHighlightItem is implemented and returns true, while ShouldSelectItem returns false.

셀 메뉴Cell Menus

UICollectionView의 각 셀은 선택적으로 지원 되는 잘라내기, 복사 및 붙여넣기를 허용 하는 메뉴를 표시할 수 있습니다.Each Cell in a UICollectionView is capable of showing a menu that allows cut, copy, and paste to optionally be supported. 셀에 대 한 편집 메뉴를 만들려면 다음을 수행 합니다.To create an edit menu on a cell:

  1. 항목이 메뉴를 표시 해야 하는 경우 ShouldShowMenu를 재정의 하 고 true를 반환 합니다.Override ShouldShowMenu and return true if the item should show a menu.
  2. CanPerformAction를 재정의 하 고 항목에서 수행할 수 있는 모든 작업 (잘라내기, 복사 또는 붙여넣기)에 대해 true를 반환 합니다.Override CanPerformAction and return true for every action that the item can perform, which will be any of cut, copy or paste.
  3. 붙여넣기 작업의 편집, 복사를 수행 하려면 PerformAction를 재정의 합니다.Override PerformAction to perform the edit, copy of paste operation.

다음 스크린샷은 셀을 길게 누르면 표시 되는 메뉴를 보여 줍니다.The following screenshot show the menu when a cell is long pressed:

레이아웃Layout

UICollectionView는 모든 요소, 셀, 보조 뷰 및 장식 보기의 위치를 UICollectionView 자체와 독립적으로 관리 하도록 허용 하는 레이아웃 시스템을 지원 합니다.UICollectionView supports a layout system that allows the positioning of all its elements, Cells, Supplementary Views and Decoration Views, to be managed independent of the UICollectionView itself. 응용 프로그램은 레이아웃 시스템을 사용 하 여이 문서에 표시 된 것과 같은 레이아웃을 지원 하 고 사용자 지정 레이아웃을 제공할 수 있습니다.Using the layout system, an application can support layouts such as the grid-like one we’ve seen in this article, as well as provide custom layouts.

레이아웃 기본 사항Layout Basics

UICollectionView의 레이아웃은 UICollectionViewLayout에서 상속 되는 클래스에 정의 됩니다.Layouts in a UICollectionView are defined in a class that inherits from UICollectionViewLayout. 레이아웃 구현은 UICollectionView의 모든 항목에 대 한 레이아웃 특성을 만드는 역할을 합니다.The layout implementation is responsible for creating the layout attributes for every item in the UICollectionView. 다음 두 가지 방법으로 레이아웃을 만들 수 있습니다.There are two ways to create a layout:

  • 기본 제공 UICollectionViewFlowLayout를 사용 합니다.Use the built-in UICollectionViewFlowLayout .
  • UICollectionViewLayout에서 상속 하 여 사용자 지정 레이아웃을 제공 합니다.Provide a custom layout by inheriting from UICollectionViewLayout .

선형 레이아웃Flow Layout

UICollectionViewFlowLayout 클래스는 표시 된 대로 셀 표에 콘텐츠를 정렬 하는 데 적합 한 줄 기반 레이아웃을 제공 합니다.The UICollectionViewFlowLayout class provides a line-based layout that suitable for arranging content in a grid of Cells as we’ve seen.

선형 레이아웃을 사용 하려면 다음을 수행 합니다.To use a flow layout:

  • UICollectionViewFlowLayout의 인스턴스를 만듭니다.Create an instance of UICollectionViewFlowLayout :
var layout = new UICollectionViewFlowLayout ();
  • 인스턴스를 UICollectionView의 생성자에 전달 합니다.Pass the instance to the constructor of the UICollectionView :
simpleCollectionViewController = new SimpleCollectionViewController (layout);

이는 표 형태로 콘텐츠를 레이아웃 하는 데 필요 합니다.This is all that is needed to layout content in a grid. 또한 방향이 변경 되 면 아래와 같이 UICollectionViewFlowLayout는 콘텐츠를 적절 하 게 다시 정렬 하는 작업을 처리 합니다.Also, when the orientation changes, the UICollectionViewFlowLayout handles rearranging the content appropriately, as shown below:

섹션 인세트Section Inset

UIContentView주위의 공간을 제공 하기 위해 레이아웃에는 UIEdgeInsets형식의 SectionInset 속성이 있습니다.To provide some space around the UIContentView, layouts have a SectionInset property of type UIEdgeInsets. 예를 들어 다음 코드는 UICollectionViewFlowLayout에 의해 배치 될 때 UIContentView의 각 섹션 주위에 50 픽셀 버퍼를 제공 합니다.For example, the following code provides a 50-pixel buffer around each section of the UIContentView when laid out by a UICollectionViewFlowLayout:

var layout = new UICollectionViewFlowLayout ();
layout.SectionInset = new UIEdgeInsets (50,50,50,50);

이렇게 하면 아래와 같이 섹션 주위에 간격이 발생 합니다.This results in spacing around the section as shown below:

UICollectionViewFlowLayout 하위 클래스Subclassing UICollectionViewFlowLayout

버전에서 직접 UICollectionViewFlowLayout를 사용 하려면 줄을 따라 콘텐츠의 레이아웃을 추가로 사용자 지정 하기 위해 서브클래싱 될 수도 있습니다.In edition to using UICollectionViewFlowLayout directly, it can also be subclassed to further customize the layout of content along a line. 예를 들어이를 사용 하 여 셀을 그리드로 줄 바꿈하지 않는 레이아웃을 만들 수 있습니다. 대신 아래와 같이 가로 스크롤 효과가 있는 단일 행을 만듭니다.For example, this can be used to create a layout that does not wrap the Cells into a grid, but instead creates a single row with a horizontal scrolling effect, as shown below:

이를 구현 하려면 UICollectionViewFlowLayout 필요 합니다.To implement this by subclassing UICollectionViewFlowLayout requires:

  • 생성자의 레이아웃 자체 또는 모든 항목에 적용 되는 레이아웃 속성을 초기화 하는 중입니다.Initializing any layout properties that apply to the layout itself or all items in the layout in the constructor.
  • ShouldInvalidateLayoutForBoundsChange를 재정의 하 여 UICollectionView의 범위가 변경 될 때 셀 레이아웃이 다시 계산 되도록 true를 반환 합니다.Overriding ShouldInvalidateLayoutForBoundsChange , returning true so that when bounds of the UICollectionView changes, the layout of the cells will be recalculated. 이 경우에는 가운데 대부분 셀에 적용 되는 변환에 대 한 코드가 스크롤 중에 적용 되도록 합니다.This is used in this case ensure the code for transformation applied to the centermost cell will be applied during scrolling.
  • TargetContentOffset를 재정의 하면 스크롤이 중지 될 때 가장 많은 셀이 UICollectionView 가운데에 맞춰집니다.Overriding TargetContentOffset to make the centermost cell snap to the center of the UICollectionView as scrolling stops.
  • UICollectionViewLayoutAttributes의 배열을 반환 하도록 LayoutAttributesForElementsInRect를 재정의 합니다.Overriding LayoutAttributesForElementsInRect to return an array of UICollectionViewLayoutAttributes . UICollectionViewLayoutAttribute에는 Center, Size, ZIndex, Transform3D 등의 속성을 포함 하 여 특정 항목을 레이아웃 하는 방법에 대 한 정보가 포함 되어 있습니다.Each UICollectionViewLayoutAttribute contains information on how to layout the particular item, including properties such as its Center , Size , ZIndex and Transform3D .

다음 코드는 이러한 구현을 보여 줍니다.The following code shows such an implementation:

using System;
using CoreGraphics;
using Foundation;
using UIKit;
using CoreGraphics;
using CoreAnimation;

namespace SimpleCollectionView
{
  public class LineLayout : UICollectionViewFlowLayout
  {
    public const float ITEM_SIZE = 200.0f;
    public const int ACTIVE_DISTANCE = 200;
    public const float ZOOM_FACTOR = 0.3f;

    public LineLayout ()
    {
      ItemSize = new CGSize (ITEM_SIZE, ITEM_SIZE);
      ScrollDirection = UICollectionViewScrollDirection.Horizontal;
            SectionInset = new UIEdgeInsets (400,0,400,0);
      MinimumLineSpacing = 50.0f;
    }

    public override bool ShouldInvalidateLayoutForBoundsChange (CGRect newBounds)
    {
      return true;
    }

    public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect (CGRect rect)
    {
      var array = base.LayoutAttributesForElementsInRect (rect);
            var visibleRect = new CGRect (CollectionView.ContentOffset, CollectionView.Bounds.Size);

      foreach (var attributes in array) {
        if (attributes.Frame.IntersectsWith (rect)) {
          float distance = (float)(visibleRect.GetMidX () - attributes.Center.X);
          float normalizedDistance = distance / ACTIVE_DISTANCE;
          if (Math.Abs (distance) < ACTIVE_DISTANCE) {
            float zoom = 1 + ZOOM_FACTOR * (1 - Math.Abs (normalizedDistance));
            attributes.Transform3D = CATransform3D.MakeScale (zoom, zoom, 1.0f);
            attributes.ZIndex = 1;
          }
        }
      }
      return array;
    }

    public override CGPoint TargetContentOffset (CGPoint proposedContentOffset, CGPoint scrollingVelocity)
    {
      float offSetAdjustment = float.MaxValue;
      float horizontalCenter = (float)(proposedContentOffset.X + (this.CollectionView.Bounds.Size.Width / 2.0));
      CGRect targetRect = new CGRect (proposedContentOffset.X, 0.0f, this.CollectionView.Bounds.Size.Width, this.CollectionView.Bounds.Size.Height);
      var array = base.LayoutAttributesForElementsInRect (targetRect);
      foreach (var layoutAttributes in array) {
        float itemHorizontalCenter = (float)layoutAttributes.Center.X;
        if (Math.Abs (itemHorizontalCenter - horizontalCenter) < Math.Abs (offSetAdjustment)) {
          offSetAdjustment = itemHorizontalCenter - horizontalCenter;
        }
      }
            return new CGPoint (proposedContentOffset.X + offSetAdjustment, proposedContentOffset.Y);
    }

  }
}

사용자 지정 레이아웃Custom Layout

UICollectionViewFlowLayout를 사용 하는 것 외에도 UICollectionViewLayout에서 직접 상속 하 여 레이아웃을 완벽 하 게 사용자 지정할 수 있습니다.In addition to using UICollectionViewFlowLayout, layouts can also be fully customized by inheriting directly from UICollectionViewLayout.

재정의할 주요 메서드는 다음과 같습니다.The key methods to override are:

  • PrepareLayout – 레이아웃 프로세스 전체에서 사용 되는 초기 기하학적 계산을 수행 하는 데 사용 됩니다.PrepareLayout – Used for performing initial geometric calculations that will be used throughout the layout process.
  • CollectionViewContentSize – 콘텐츠를 표시 하는 데 사용 되는 영역의 크기를 반환 합니다.CollectionViewContentSize – Returns the size of the area used to display content.
  • LayoutAttributesForElementsInRect – 앞서 표시 된 UICollectionViewFlowLayout 예제와 마찬가지로이 메서드는 각 항목의 레이아웃 방법과 관련 하 여 UICollectionView에 정보를 제공 하는 데 사용 됩니다.LayoutAttributesForElementsInRect – As with the UICollectionViewFlowLayout example shown earlier, this method is used to provide information to the UICollectionView regarding how to layout each item. 그러나 UICollectionViewFlowLayout와 달리 사용자 지정 레이아웃을 만들 때 선택한 항목의 위치를 지정할 수 있습니다.However, unlike the UICollectionViewFlowLayout , when creating a custom layout, you can position items however you choose.

예를 들어 아래와 같이 동일한 콘텐츠가 원형 레이아웃으로 표시 될 수 있습니다.For example, the same content could be presented in a circular layout as shown below:

레이아웃에 대 한 장점은 표 형식 레이아웃에서 가로 스크롤 레이아웃으로 변경 하는 것이 고 이후에이 원형 레이아웃을 사용 하려면 UICollectionView에 제공 된 레이아웃 클래스만 변경 하면 됩니다.The powerful thing about layouts is that to change from the grid-like layout, to a horizontal scrolling layout, and subsequently to this circular layout requires only the layout class provided to the UICollectionView be changed. UICollectionView의 대리자 또는 데이터 소스 코드는 전혀 변경 되지 않습니다.Nothing in the UICollectionView, its delegate or data source code changes at all.

IOS 9의 변경 내용Changes in iOS 9

IOS 9에서 컬렉션 뷰 (UICollectionView)는 이제 새 기본 제스처 인식자와 몇 가지 새로운 지원 메서드를 추가 하 여 항목의 항목을 끌어서 다시 정렬 하는 것을 지원 합니다.In iOS 9, the collection view (UICollectionView) now supports drag reordering of items out of the box by adding a new default gesture recognizer and several new supporting methods.

이러한 새 메서드를 사용 하 여 컬렉션 뷰에서 순서를 변경 하는 작업을 쉽게 구현할 수 있으며 다시 정렬 프로세스의 모든 단계에서 항목 모양을 사용자 지정 하는 옵션을 사용할 수 있습니다.Using these new methods, you can easily implement drag to reorder in your collection view and have the option of customizing the items appearance during any stage of the reordering process.

이 문서에서는 Xamarin.ios 응용 프로그램에서 다시 정렬을 구현 하는 방법 및 컬렉션 뷰 컨트롤에서 iOS 9가 만든 다른 변경 내용 중 일부를 살펴보겠습니다.In this article, we'll take a look at implementing drag-to-reorder in a Xamarin.iOS application as well as some of the other changes iOS 9 has made to the collection view control:

항목 다시 정렬Reordering of Items

위에서 설명한 것 처럼, iOS 9의 컬렉션 보기에 대 한 가장 중요 한 변경 사항 중 하나는 즉시 끌어서 재주문 기능을 즉시 활용 하는 것 이었습니다.As stated above, one of the most significant changes to the collection view in iOS 9 was the addition of easy drag-to-reorder functionality out of the box.

IOS 9에서 컬렉션 뷰에 다시 정렬을 추가 하는 가장 빠른 방법은 UICollectionViewController를 사용 하는 것입니다.In iOS 9, the quickest way to add reordering to a collection view is to use a UICollectionViewController. 이제 컬렉션 뷰 컨트롤러에는 컬렉션의 항목을 다시 정렬 하기 위해 끌기를 지 원하는 표준 제스처 인식기 를 추가 하는 InstallsStandardGestureForInteractiveMovement 속성이 있습니다.The collection view controller now has a InstallsStandardGestureForInteractiveMovement property, which adds a standard gesture recognizer that supports dragging to reorder items in the collection. 기본값은 true이므로 UICollectionViewDataSource 클래스의 MoveItem 메서드만 구현 하 여 순서 변경을 지원 해야 합니다.Since the default value is true, you only have to implement the MoveItem method of the UICollectionViewDataSource class to support drag-to-reorder. 예를 들면,For example:

public override void MoveItem (UICollectionView collectionView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath)
{
  // Reorder our list of items
  ...
}

간단한 다시 정렬 예제Simple Reordering Example

새 Xamarin.ios 프로젝트를 시작 하 고 기본 storyboard 파일을 편집 하는 간단한 예제입니다.As a quick example, start a new Xamarin.iOS project and edit the Main.storyboard file. UICollectionViewController를 디자인 화면으로 끌어 옵니다.Drag a UICollectionViewController onto the design surface:

컬렉션 뷰를 선택 합니다 (문서 개요에서이 작업을 수행 하는 것이 가장 쉽습니다.).Select the Collection View (It may be easiest to do this from the document outline). Properties Pad의 레이아웃 탭에서 아래 스크린샷에 나와 있는 것 처럼 다음 크기를 설정 합니다.In the layout tab of the Properties Pad, set the following sizes, as illustrated in the screenshot below:

  • 셀 크기: Width – 60 | 높이 – 60Cell Size: Width – 60 | Height – 60
  • 헤더 크기: Width – 0 | 높이 – 0Header Size: Width – 0 | Height – 0
  • 바닥글 크기: Width – 0 | 높이 – 0Footer Size: Width – 0 | Height – 0
  • 최소 간격:-8 셀의 경우 줄-8Min Spacing: For Cells – 8 | For Lines – 8
  • 섹션 인세트: 위쪽 – 16 | 아래쪽 – 16 | Left – 16 | 오른쪽 – 16Section Insets: Top – 16 | Bottom – 16 | Left – 16 | Right – 16

다음으로 기본 셀을 편집 합니다.Next, edit the default Cell:

  • 배경색을 파란색으로 변경Change its background color to blue
  • 셀 제목으로 사용할 레이블 추가Add a label to act as the title for the cell
  • 다시 사용 식별자를 로 설정Set the reuse identifier to cell

크기가 변경 될 때 셀 내부에 레이블을 유지 하는 제약 조건을 추가 합니다.Add constraints to keep the Label centered inside the cell as it changes size:

Collectionviewcell속성 패드 에서 클래스TextCollectionViewCell설정 합니다.In the Property Pad for the CollectionViewCell and set the Class to TextCollectionViewCell:

다시 사용할 수 있는 뷰의 컬렉션Cell로 설정 합니다.Set the Collection Reusable View to Cell:

마지막으로 레이블을 선택 하 고 TextLabel이름을 다음과 같이 선택 합니다.Finally, select the Label and name it TextLabel:

TextCollectionViewCell 클래스를 편집 하 고 다음 속성을 추가 합니다.Edit the TextCollectionViewCell class and add the following properties.:

using System;
using Foundation;
using UIKit;

namespace CollectionView
{
  public partial class TextCollectionViewCell : UICollectionViewCell
  {
    #region Computed Properties
    public string Title {
      get { return TextLabel.Text; }
      set { TextLabel.Text = value; }
    }
    #endregion

    #region Constructors
    public TextCollectionViewCell (IntPtr handle) : base (handle)
    {
    }
    #endregion
  }
}

여기서 레이블의 Text 속성은 셀의 제목으로 노출 되므로 코드에서 설정할 수 있습니다.Here the Text property of the label is exposed as the title of the cell, so it can be set from code.

프로젝트에 새 C# 클래스를 추가 하 고WaterfallCollectionSource호출 합니다.Add a new C# class to the project and call it WaterfallCollectionSource. 파일을 편집 하 여 다음과 같이 만듭니다.Edit the file and make it look like the following:

using System;
using Foundation;
using UIKit;
using System.Collections.Generic;

namespace CollectionView
{
  public class WaterfallCollectionSource : UICollectionViewDataSource
  {
    #region Computed Properties
    public WaterfallCollectionView CollectionView { get; set;}
    public List<int> Numbers { get; set; } = new List<int> ();
    #endregion

    #region Constructors
    public WaterfallCollectionSource (WaterfallCollectionView collectionView)
    {
      // Initialize
      CollectionView = collectionView;

      // Init numbers collection
      for (int n = 0; n < 100; ++n) {
        Numbers.Add (n);
      }
    }
    #endregion

    #region Override Methods
    public override nint NumberOfSections (UICollectionView collectionView) {
      // We only have one section
      return 1;
    }

    public override nint GetItemsCount (UICollectionView collectionView, nint section) {
      // Return the number of items
      return Numbers.Count;
    }

    public override UICollectionViewCell GetCell (UICollectionView collectionView, NSIndexPath indexPath)
    {
      // Get a reusable cell and set {~~it's~>its~~} title from the item
      var cell = collectionView.DequeueReusableCell ("Cell", indexPath) as TextCollectionViewCell;
      cell.Title = Numbers [(int)indexPath.Item].ToString();

      return cell;
    }

    public override bool CanMoveItem (UICollectionView collectionView, NSIndexPath indexPath) {
      // We can always move items
      return true;
    }

    public override void MoveItem (UICollectionView collectionView, NSIndexPath sourceIndexPath, NSIndexPath destinationIndexPath)
    {
      // Reorder our list of items
      var item = Numbers [(int)sourceIndexPath.Item];
      Numbers.RemoveAt ((int)sourceIndexPath.Item);
      Numbers.Insert ((int)destinationIndexPath.Item, item);
    }
    #endregion
  }
}

이 클래스는 컬렉션 뷰의 데이터 소스가 되며 컬렉션의 각 셀에 대 한 정보를 제공 합니다.This class will be the data source for our collection view and provide the information for each cell in the collection. MoveItem 메서드는 컬렉션의 항목을 끌어서 다시 정렬할 수 있도록 구현 됩니다.Notice that the MoveItem method is implemented to allow items in the collection to be drag reordered.

프로젝트에 다른 C# 새 클래스를 추가 하 고WaterfallCollectionDelegate호출 합니다.Add another new C# class to the project and call it WaterfallCollectionDelegate. 이 파일을 편집 하 여 다음과 같이 만듭니다.Edit this file and make it look like the following:

using System;
using Foundation;
using UIKit;
using System.Collections.Generic;

namespace CollectionView
{
  public class WaterfallCollectionDelegate : UICollectionViewDelegate
  {
    #region Computed Properties
    public WaterfallCollectionView CollectionView { get; set;}
    #endregion

    #region Constructors
    public WaterfallCollectionDelegate (WaterfallCollectionView collectionView)
    {

      // Initialize
      CollectionView = collectionView;

    }
    #endregion

    #region Overrides Methods
    public override bool ShouldHighlightItem (UICollectionView collectionView, NSIndexPath indexPath) {
      // Always allow for highlighting
      return true;
    }

    public override void ItemHighlighted (UICollectionView collectionView, NSIndexPath indexPath)
    {
      // Get cell and change to green background
      var cell = collectionView.CellForItem(indexPath);
      cell.ContentView.BackgroundColor = UIColor.FromRGB(183,208,57);
    }

    public override void ItemUnhighlighted (UICollectionView collectionView, NSIndexPath indexPath)
    {
      // Get cell and return to blue background
      var cell = collectionView.CellForItem(indexPath);
      cell.ContentView.BackgroundColor = UIColor.FromRGB(164,205,255);
    }
    #endregion
  }
}

컬렉션 뷰의 대리자 역할을 합니다.This will act as the delegate for our collection view. 메서드는 사용자가 컬렉션 뷰에서 상호 작용할 때 셀을 강조 표시 하도록 재정의 되었습니다.Methods have been overridden to highlight a cell as the user interacts with it in the collection view.

프로젝트에 마지막 C# 클래스 하나를 추가 하 고WaterfallCollectionView호출 합니다.Add one last C# class to the project and call it WaterfallCollectionView. 이 파일을 편집 하 여 다음과 같이 만듭니다.Edit this file and make it look like the following:

using System;
using UIKit;
using System.Collections.Generic;
using Foundation;

namespace CollectionView
{
  [Register("WaterfallCollectionView")]
  public class WaterfallCollectionView : UICollectionView
  {

    #region Constructors
    public WaterfallCollectionView (IntPtr handle) : base (handle)
    {
    }
    #endregion

    #region Override Methods
    public override void AwakeFromNib ()
    {
      base.AwakeFromNib ();

      // Initialize
      DataSource = new WaterfallCollectionSource(this);
      Delegate = new WaterfallCollectionDelegate(this);

    }
    #endregion
  }
}

위에서 만든 DataSourceDelegate는 해당 storyboard (또는 xib 파일)에서 컬렉션 뷰를 생성할 때 설정 됩니다.Notice that DataSource and Delegate that we created above are set when the collection view is constructed from its storyboard (or .xib file).

주 storyboard 파일을 다시 편집 하 고 컬렉션 뷰를 선택한 다음 속성으로 전환 합니다.Edit the Main.storyboard file again and select the collection view and switch to the Properties. 클래스 를 위에서 정의한 사용자 지정 WaterfallCollectionView 클래스로 설정 합니다.Set the Class to the custom WaterfallCollectionView class that we defined above:

UI에 대 한 변경 내용을 저장 하 고 앱을 실행 합니다.Save the changes you made to the UI and run the app. 사용자가 목록에서 항목을 선택 하 여 새 위치로 끌면 항목을 이동할 때 다른 항목이 자동으로 애니메이션 효과를 적용 합니다.If the user selects an item from the list and drags it to a new location, the other items will animate automatically as they move out of the way of the item. 사용자가 새 위치에서 항목을 삭제 하면 해당 위치에 그대로 유지 됩니다.When the user drops the item in a new location, it will stick to that location. 예를 들면,For example:

사용자 지정 제스처 인식기 사용Using a Custom Gesture Recognizer

UICollectionViewController를 사용할 수 없고 일반 UIViewController를 사용 해야 하거나 끌어서 놓기 제스처를 더 많이 제어 하려는 경우에는 사용자 지정 제스처 인식기를 만들어 뷰가 로드 될 때 컬렉션 뷰에 추가할 수 있습니다.In cases where you cannot use a UICollectionViewController and must use a regular UIViewController, or if you wish to take more control over the drag-and-drop gesture, you can create your own custom Gesture Recognizer and add it to the Collection View when the View loads. 예를 들면,For example:

public override void ViewDidLoad ()
{
  base.ViewDidLoad ();

  // Create a custom gesture recognizer
  var longPressGesture = new UILongPressGestureRecognizer ((gesture) => {

    // Take action based on state
    switch(gesture.State) {
    case UIGestureRecognizerState.Began:
      var selectedIndexPath = CollectionView.IndexPathForItemAtPoint(gesture.LocationInView(View));
      if (selectedIndexPath !=null) {
        CollectionView.BeginInteractiveMovementForItem(selectedIndexPath);
      }
      break;
    case UIGestureRecognizerState.Changed:
      CollectionView.UpdateInteractiveMovementTargetPosition(gesture.LocationInView(View));
      break;
    case UIGestureRecognizerState.Ended:
      CollectionView.EndInteractiveMovement();
      break;
    default:
      CollectionView.CancelInteractiveMovement();
      break;
    }

  });

  // Add the custom recognizer to the collection view
  CollectionView.AddGestureRecognizer(longPressGesture);
}

여기서는 컬렉션 뷰에 추가 된 몇 가지 새로운 메서드를 사용 하 여 끌기 작업을 구현 하 고 제어 합니다.Here we are using several new methods added to the collection view to implement and control the drag operation:

  • BeginInteractiveMovementForItem-이동 작업의 시작을 표시 합니다.BeginInteractiveMovementForItem - Marks the start of a move operation.
  • UpdateInteractiveMovementTargetPosition-항목의 위치가 업데이트 될 때 전송 됩니다.UpdateInteractiveMovementTargetPosition - Is sent as the item's location is updated.
  • EndInteractiveMovement-항목 이동의 끝을 표시 합니다.EndInteractiveMovement - Marks the end of an item move.
  • CancelInteractiveMovement-이동 작업을 취소 하는 사용자를 표시 합니다.CancelInteractiveMovement - Marks the user canceling the move operation.

응용 프로그램이 실행 될 때 끌기 작업은 컬렉션 뷰와 함께 제공 되는 기본 끌기 제스처 인식기와 똑같이 작동 합니다.When the application is run, the drag operation will work exactly like the default drag gesture recognizer that comes with the collection view.

사용자 지정 레이아웃 및 순서 바꾸기Custom Layouts and Reordering

IOS 9에는 컬렉션 뷰에서 순서를 변경 하 고 사용자 지정 레이아웃을 사용 하 여 작업 하기 위한 몇 가지 새로운 메서드가 추가 되었습니다.In iOS 9, several new methods have been added to work with drag-to-reorder and custom layouts in a collection view. 이 기능을 탐색 하기 위해 컬렉션에 사용자 지정 레이아웃을 추가 해 보겠습니다.To explore this feature, let's add a custom layout to the collection.

먼저WaterfallCollectionLayout라는 새 C# 클래스를 프로젝트에 추가 합니다.First, add a new C# class called WaterfallCollectionLayout to the project. 편집 하 고 다음과 같이 만듭니다.Edit it and make it look like the following:

using System;
using Foundation;
using UIKit;
using System.Collections.Generic;
using CoreGraphics;

namespace CollectionView
{
  [Register("WaterfallCollectionLayout")]
  public class WaterfallCollectionLayout : UICollectionViewLayout
  {
    #region Private Variables
    private int columnCount = 2;
    private nfloat minimumColumnSpacing = 10;
    private nfloat minimumInterItemSpacing = 10;
    private nfloat headerHeight = 0.0f;
    private nfloat footerHeight = 0.0f;
    private UIEdgeInsets sectionInset = new UIEdgeInsets(0, 0, 0, 0);
    private WaterfallCollectionRenderDirection itemRenderDirection = WaterfallCollectionRenderDirection.ShortestFirst;
    private Dictionary<nint,UICollectionViewLayoutAttributes> headersAttributes = new Dictionary<nint, UICollectionViewLayoutAttributes>();
    private Dictionary<nint,UICollectionViewLayoutAttributes> footersAttributes = new Dictionary<nint, UICollectionViewLayoutAttributes>();
    private List<CGRect> unionRects = new List<CGRect>();
    private List<nfloat> columnHeights = new List<nfloat>();
    private List<UICollectionViewLayoutAttributes> allItemAttributes = new List<UICollectionViewLayoutAttributes>();
    private List<List<UICollectionViewLayoutAttributes>> sectionItemAttributes = new List<List<UICollectionViewLayoutAttributes>>();
    private nfloat unionSize = 20;
    #endregion

    #region Computed Properties
    [Export("ColumnCount")]
    public int ColumnCount {
      get { return columnCount; }
      set {
        WillChangeValue ("ColumnCount");
        columnCount = value;
        DidChangeValue ("ColumnCount");

        InvalidateLayout ();
      }
    }

    [Export("MinimumColumnSpacing")]
    public nfloat MinimumColumnSpacing {
      get { return minimumColumnSpacing; }
      set {
        WillChangeValue ("MinimumColumnSpacing");
        minimumColumnSpacing = value;
        DidChangeValue ("MinimumColumnSpacing");

        InvalidateLayout ();
      }
    }

    [Export("MinimumInterItemSpacing")]
    public nfloat MinimumInterItemSpacing {
      get { return minimumInterItemSpacing; }
      set {
        WillChangeValue ("MinimumInterItemSpacing");
        minimumInterItemSpacing = value;
        DidChangeValue ("MinimumInterItemSpacing");

        InvalidateLayout ();
      }
    }

    [Export("HeaderHeight")]
    public nfloat HeaderHeight {
      get { return headerHeight; }
      set {
        WillChangeValue ("HeaderHeight");
        headerHeight = value;
        DidChangeValue ("HeaderHeight");

        InvalidateLayout ();
      }
    }

    [Export("FooterHeight")]
    public nfloat FooterHeight {
      get { return footerHeight; }
      set {
        WillChangeValue ("FooterHeight");
        footerHeight = value;
        DidChangeValue ("FooterHeight");

        InvalidateLayout ();
      }
    }

    [Export("SectionInset")]
    public UIEdgeInsets SectionInset {
      get { return sectionInset; }
      set {
        WillChangeValue ("SectionInset");
        sectionInset = value;
        DidChangeValue ("SectionInset");

        InvalidateLayout ();
      }
    }

    [Export("ItemRenderDirection")]
    public WaterfallCollectionRenderDirection ItemRenderDirection {
      get { return itemRenderDirection; }
      set {
        WillChangeValue ("ItemRenderDirection");
        itemRenderDirection = value;
        DidChangeValue ("ItemRenderDirection");

        InvalidateLayout ();
      }
    }
    #endregion

    #region Constructors
    public WaterfallCollectionLayout ()
    {
    }

    public WaterfallCollectionLayout(NSCoder coder) : base(coder) {

    }
    #endregion

    #region Public Methods
    public nfloat ItemWidthInSectionAtIndex(int section) {

      var width = CollectionView.Bounds.Width - SectionInset.Left - SectionInset.Right;
      return (nfloat)Math.Floor ((width - ((ColumnCount - 1) * MinimumColumnSpacing)) / ColumnCount);
    }
    #endregion

    #region Override Methods
    public override void PrepareLayout ()
    {
      base.PrepareLayout ();

      // Get the number of sections
      var numberofSections = CollectionView.NumberOfSections();
      if (numberofSections == 0)
        return;

      // Reset collections
      headersAttributes.Clear ();
      footersAttributes.Clear ();
      unionRects.Clear ();
      columnHeights.Clear ();
      allItemAttributes.Clear ();
      sectionItemAttributes.Clear ();

      // Initialize column heights
      for (int n = 0; n < ColumnCount; n++) {
        columnHeights.Add ((nfloat)0);
      }

      // Process all sections
      nfloat top = 0.0f;
      var attributes = new UICollectionViewLayoutAttributes ();
      var columnIndex = 0;
      for (nint section = 0; section < numberofSections; ++section) {
        // Calculate section specific metrics
        var minimumInterItemSpacing = (MinimumInterItemSpacingForSection == null) ? MinimumColumnSpacing :
          MinimumInterItemSpacingForSection (CollectionView, this, section);

        // Calculate widths
        var width = CollectionView.Bounds.Width - SectionInset.Left - SectionInset.Right;
        var itemWidth = (nfloat)Math.Floor ((width - ((ColumnCount - 1) * MinimumColumnSpacing)) / ColumnCount);

        // Calculate section header
        var heightHeader = (HeightForHeader == null) ? HeaderHeight :
          HeightForHeader (CollectionView, this, section);

        if (heightHeader > 0) {
          attributes = UICollectionViewLayoutAttributes.CreateForSupplementaryView (UICollectionElementKindSection.Header, NSIndexPath.FromRowSection (0, section));
          attributes.Frame = new CGRect (0, top, CollectionView.Bounds.Width, heightHeader);
          headersAttributes.Add (section, attributes);
          allItemAttributes.Add (attributes);

          top = attributes.Frame.GetMaxY ();
        }

        top += SectionInset.Top;
        for (int n = 0; n < ColumnCount; n++) {
          columnHeights [n] = top;
        }

        // Calculate Section Items
        var itemCount = CollectionView.NumberOfItemsInSection(section);
        List<UICollectionViewLayoutAttributes> itemAttributes = new List<UICollectionViewLayoutAttributes> ();

        for (nint n = 0; n < itemCount; n++) {
          var indexPath = NSIndexPath.FromRowSection (n, section);
          columnIndex = NextColumnIndexForItem (n);
          var xOffset = SectionInset.Left + (itemWidth + MinimumColumnSpacing) * (nfloat)columnIndex;
          var yOffset = columnHeights [columnIndex];
          var itemSize = (SizeForItem == null) ? new CGSize (0, 0) : SizeForItem (CollectionView, this, indexPath);
          nfloat itemHeight = 0.0f;

          if (itemSize.Height > 0.0f && itemSize.Width > 0.0f) {
            itemHeight = (nfloat)Math.Floor (itemSize.Height * itemWidth / itemSize.Width);
          }

          attributes = UICollectionViewLayoutAttributes.CreateForCell (indexPath);
          attributes.Frame = new CGRect (xOffset, yOffset, itemWidth, itemHeight);
          itemAttributes.Add (attributes);
          allItemAttributes.Add (attributes);
          columnHeights [columnIndex] = attributes.Frame.GetMaxY () + MinimumInterItemSpacing;
        }
        sectionItemAttributes.Add (itemAttributes);

        // Calculate Section Footer
        nfloat footerHeight = 0.0f;
        columnIndex = LongestColumnIndex();
        top = columnHeights [columnIndex] - MinimumInterItemSpacing + SectionInset.Bottom;
        footerHeight = (HeightForFooter == null) ? FooterHeight : HeightForFooter(CollectionView, this, section);

        if (footerHeight > 0) {
          attributes = UICollectionViewLayoutAttributes.CreateForSupplementaryView (UICollectionElementKindSection.Footer, NSIndexPath.FromRowSection (0, section));
          attributes.Frame = new CGRect (0, top, CollectionView.Bounds.Width, footerHeight);
          footersAttributes.Add (section, attributes);
          allItemAttributes.Add (attributes);
          top = attributes.Frame.GetMaxY ();
        }

        for (int n = 0; n < ColumnCount; n++) {
          columnHeights [n] = top;
        }
      }

      var i =0;
      var attrs = allItemAttributes.Count;
      while(i < attrs) {
        var rect1 = allItemAttributes [i].Frame;
        i = (int)Math.Min (i + unionSize, attrs) - 1;
        var rect2 = allItemAttributes [i].Frame;
        unionRects.Add (CGRect.Union (rect1, rect2));
        i++;
      }

    }

    public override CGSize CollectionViewContentSize {
      get {
        if (CollectionView.NumberOfSections () == 0) {
          return new CGSize (0, 0);
        }

        var contentSize = CollectionView.Bounds.Size;
        contentSize.Height = columnHeights [0];
        return contentSize;
      }
    }

    public override UICollectionViewLayoutAttributes LayoutAttributesForItem (NSIndexPath indexPath)
    {
      if (indexPath.Section >= sectionItemAttributes.Count) {
        return null;
      }

      if (indexPath.Item >= sectionItemAttributes [indexPath.Section].Count) {
        return null;
      }

      var list = sectionItemAttributes [indexPath.Section];
      return list [(int)indexPath.Item];
    }

    public override UICollectionViewLayoutAttributes LayoutAttributesForSupplementaryView (NSString kind, NSIndexPath indexPath)
    {
      var attributes = new UICollectionViewLayoutAttributes ();

      switch (kind) {
      case "header":
        attributes = headersAttributes [indexPath.Section];
        break;
      case "footer":
        attributes = footersAttributes [indexPath.Section];
        break;
      }

      return attributes;
    }

    public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect (CGRect rect)
    {
      var begin = 0;
      var end = unionRects.Count;
      List<UICollectionViewLayoutAttributes> attrs = new List<UICollectionViewLayoutAttributes> ();

      for (int i = 0; i < end; i++) {
        if (rect.IntersectsWith(unionRects[i])) {
          begin = i * (int)unionSize;
        }
      }

      for (int i = end - 1; i >= 0; i--) {
        if (rect.IntersectsWith (unionRects [i])) {
          end = (int)Math.Min ((i + 1) * (int)unionSize, allItemAttributes.Count);
          break;
        }
      }

      for (int i = begin; i < end; i++) {
        var attr = allItemAttributes [i];
        if (rect.IntersectsWith (attr.Frame)) {
          attrs.Add (attr);
        }
      }

      return attrs.ToArray();
    }

    public override bool ShouldInvalidateLayoutForBoundsChange (CGRect newBounds)
    {
      var oldBounds = CollectionView.Bounds;
      return (newBounds.Width != oldBounds.Width);
    }
    #endregion

    #region Private Methods
    private int ShortestColumnIndex() {
      var index = 0;
      var shortestHeight = nfloat.MaxValue;
      var n = 0;

      // Scan each column for the shortest height
      foreach (nfloat height in columnHeights) {
        if (height < shortestHeight) {
          shortestHeight = height;
          index = n;
        }
        ++n;
      }

      return index;
    }

    private int LongestColumnIndex() {
      var index = 0;
      var longestHeight = nfloat.MinValue;
      var n = 0;

      // Scan each column for the shortest height
      foreach (nfloat height in columnHeights) {
        if (height > longestHeight) {
          longestHeight = height;
          index = n;
        }
        ++n;
      }

      return index;
    }

    private int NextColumnIndexForItem(nint item) {
      var index = 0;

      switch (ItemRenderDirection) {
      case WaterfallCollectionRenderDirection.ShortestFirst:
        index = ShortestColumnIndex ();
        break;
      case WaterfallCollectionRenderDirection.LeftToRight:
        index = ColumnCount;
        break;
      case WaterfallCollectionRenderDirection.RightToLeft:
        index = (ColumnCount - 1) - ((int)item / ColumnCount);
        break;
      }

      return index;
    }
    #endregion

    #region Events
    public delegate CGSize WaterfallCollectionSizeDelegate(UICollectionView collectionView, WaterfallCollectionLayout layout, NSIndexPath indexPath);
    public delegate nfloat WaterfallCollectionFloatDelegate(UICollectionView collectionView, WaterfallCollectionLayout layout, nint section);
    public delegate UIEdgeInsets WaterfallCollectionEdgeInsetsDelegate(UICollectionView collectionView, WaterfallCollectionLayout layout, nint section);

    public event WaterfallCollectionSizeDelegate SizeForItem;
    public event WaterfallCollectionFloatDelegate HeightForHeader;
    public event WaterfallCollectionFloatDelegate HeightForFooter;
    public event WaterfallCollectionEdgeInsetsDelegate InsetForSection;
    public event WaterfallCollectionFloatDelegate MinimumInterItemSpacingForSection;
    #endregion
  }
}

이 클래스를 사용 하 여 사용자 지정 두 열인 폭포 형식 레이아웃을 컬렉션 뷰에 제공할 수 있습니다.This can be used class to provide a custom two column, waterfall type layout to the collection view. 이 코드는 WillChangeValueDidChangeValue 메서드를 통해 키-값 코딩을 사용 하 여이 클래스의 계산 된 속성에 대 한 데이터 바인딩을 제공 합니다.The code uses Key-Value Coding (via the WillChangeValue and DidChangeValue methods) to provide data binding for our computed properties in this class.

그런 다음 WaterfallCollectionSource를 편집 하 고 다음과 같이 변경 및 추가 합니다.Next, edit the WaterfallCollectionSource and make the following changes and additions:

private Random rnd = new Random();
...

public List<nfloat> Heights { get; set; } = new List<nfloat> ();
...

public WaterfallCollectionSource (WaterfallCollectionView collectionView)
{
  // Initialize
  CollectionView = collectionView;

  // Init numbers collection
  for (int n = 0; n < 100; ++n) {
    Numbers.Add (n);
    Heights.Add (rnd.Next (0, 100) + 40.0f);
  }
}

이렇게 하면 목록에 표시 되는 각 항목에 대해 임의 높이가 생성 됩니다.This will create a random height for each of the items that will be displayed in the list.

그런 다음 WaterfallCollectionView 클래스를 편집 하 고 다음 도우미 속성을 추가 합니다.Next, edit the WaterfallCollectionView class and add the following helper property:

public WaterfallCollectionSource Source {
  get { return (WaterfallCollectionSource)DataSource; }
}

이렇게 하면 사용자 지정 레이아웃에서 데이터 소스와 항목 높이를 쉽게 가져올 수 있습니다.This will make it easier to get at our data source (and the item heights) from the custom layout.

마지막으로 뷰 컨트롤러를 편집 하 고 다음 코드를 추가 합니다.Finally, edit the view controller and add the following code:

public override void AwakeFromNib ()
{
  base.AwakeFromNib ();

  var waterfallLayout = new WaterfallCollectionLayout ();

  // Wireup events
  waterfallLayout.SizeForItem += (collectionView, layout, indexPath) => {
    var collection = collectionView as WaterfallCollectionView;
    return new CGSize((View.Bounds.Width-40)/3,collection.Source.Heights[(int)indexPath.Item]);
  };

  // Attach the custom layout to the collection
  CollectionView.SetCollectionViewLayout(waterfallLayout, false);
}

그러면 사용자 지정 레이아웃의 인스턴스가 생성 되 고, 각 항목의 크기를 제공 하는 이벤트를 설정 하 고, 새 레이아웃을 컬렉션 뷰에 연결 합니다.This creates an instance of our custom layout, sets the event to provide the size of each item and attaches the new layout to our collection view.

Xamarin.ios 앱을 다시 실행 하는 경우 컬렉션 뷰는 다음과 같습니다.If we run the Xamarin.iOS app again, the collection view will now look like the following:

이전 처럼 항목을 다시 정렬할 수 있지만 이제 항목을 놓을 때 새 위치에 맞게 크기가 변경 됩니다.We can still drag-to-reorder items as before, but the items will now change size to fit their new location when they are dropped.

컬렉션 뷰 변경 내용Collection View Changes

다음 섹션에서는 iOS 9를 통해 컬렉션 보기의 각 클래스에 대 한 변경 내용을 자세히 살펴보겠습니다.In the following sections, we'll take a detailed look at the changes made to each class in the collection view by iOS 9.

UICollectionViewUICollectionView

IOS 9에 대 한 UICollectionView 클래스가 다음과 같이 변경 되거나 추가 되었습니다.The following changes or additions have been made to the UICollectionView class for iOS 9:

  • BeginInteractiveMovementForItem – 끌기 작업의 시작을 표시 합니다.BeginInteractiveMovementForItem – Marks the start of a drag operation.
  • CancelInteractiveMovement – 사용자가 끌기 작업을 취소 했음을 컬렉션 뷰에 알립니다.CancelInteractiveMovement – Informs the collection view that the user has canceled a drag operation.
  • EndInteractiveMovement – 사용자가 끌기 작업을 완료 했음을 컬렉션 뷰에 알립니다.EndInteractiveMovement – Informs the collection view that the user has finished a drag operation.
  • GetIndexPathsForVisibleSupplementaryElements – 컬렉션 뷰 섹션에서 머리글 또는 바닥글의 indexPath 반환 합니다.GetIndexPathsForVisibleSupplementaryElements – Returns the indexPath of a header or footer in a collection view section.
  • GetSupplementaryView – 지정 된 머리글 또는 바닥글을 반환 합니다.GetSupplementaryView – Returns the given header or footer.
  • GetVisibleSupplementaryViews – 표시 되는 모든 머리글 및 바닥글의 목록을 반환 합니다.GetVisibleSupplementaryViews – Returns a list of all visible header and footers.
  • UpdateInteractiveMovementTargetPosition – 끌기 작업을 수행 하는 동안 사용자가 항목을 이동 하거나 이동 했음을 컬렉션 뷰에 알립니다.UpdateInteractiveMovementTargetPosition – Informs the collection view that the user has moved, or is moving, an item during a drag operation.

UICollectionViewControllerUICollectionViewController

IOS 9의 UICollectionViewController 클래스에 대 한 다음과 같이 변경 되거나 추가 되었습니다.The following changes or additions have been made to the UICollectionViewController class in iOS 9:

  • InstallsStandardGestureForInteractiveMovementtrue를 자동으로 다시 정렬 하도록 지 원하는 새 제스처 인식기가 사용 됩니다.InstallsStandardGestureForInteractiveMovement – If true the new Gesture Recognizer that automatically supports drag-to-reorder will be used.
  • CanMoveItem – 지정 된 항목을 끌어서 다시 정렬할 수 있는 경우 컬렉션 뷰에 알립니다.CanMoveItem – Informs the collection view if a given item can be drag reordered.
  • GetTargetContentOffset – 지정 된 컬렉션 뷰 항목의 오프셋을 가져오는 데 사용 됩니다.GetTargetContentOffset – Used to get the offset of a given collection view item.
  • GetTargetIndexPathForMove – 끌기 작업에 대해 지정 된 항목의 indexPath를 가져옵니다.GetTargetIndexPathForMove – Gets the indexPath of a given item for a drag operation.
  • MoveItem – 목록에서 지정 된 항목의 순서를 이동 합니다.MoveItem – Moves the order of a given item in the list.

UICollectionViewDataSourceUICollectionViewDataSource

IOS 9의 UICollectionViewDataSource 클래스에 대 한 다음과 같이 변경 되거나 추가 되었습니다.The following changes or additions have been made to the UICollectionViewDataSource class in iOS 9:

  • CanMoveItem – 지정 된 항목을 끌어서 다시 정렬할 수 있는 경우 컬렉션 뷰에 알립니다.CanMoveItem – Informs the collection view if a given item can be drag reordered.
  • MoveItem – 목록에서 지정 된 항목의 순서를 이동 합니다.MoveItem – Moves the order of a given item in the list.

UICollectionViewDelegateUICollectionViewDelegate

IOS 9의 UICollectionViewDelegate 클래스에 대 한 다음과 같이 변경 되거나 추가 되었습니다.The following changes or additions have been made to the UICollectionViewDelegate class in iOS 9:

  • GetTargetContentOffset – 지정 된 컬렉션 뷰 항목의 오프셋을 가져오는 데 사용 됩니다.GetTargetContentOffset – Used to get the offset of a given collection view item.
  • GetTargetIndexPathForMove – 끌기 작업에 대해 지정 된 항목의 indexPath를 가져옵니다.GetTargetIndexPathForMove – Gets the indexPath of a given item for a drag operation.

UICollectionViewFlowLayoutUICollectionViewFlowLayout

IOS 9의 UICollectionViewFlowLayout 클래스에 대 한 다음과 같이 변경 되거나 추가 되었습니다.The following changes or additions have been made to the UICollectionViewFlowLayout class in iOS 9:

  • SectionFootersPinToVisibleBounds – 표시 된 컬렉션 보기 범위에 섹션 바닥글을 표시 합니다.SectionFootersPinToVisibleBounds – Sticks the section footers to the visible collection view bounds.
  • SectionHeadersPinToVisibleBounds – 섹션 헤더를 표시 된 컬렉션 뷰 범위에 표시 합니다.SectionHeadersPinToVisibleBounds – Sticks the section headers to the visible collection view bounds.

UICollectionViewLayoutUICollectionViewLayout

IOS 9의 UICollectionViewLayout 클래스에 대 한 다음과 같이 변경 되거나 추가 되었습니다.The following changes or additions have been made to the UICollectionViewLayout class in iOS 9:

  • GetInvalidationContextForEndingInteractiveMovementOfItems – 사용자가 끌기를 완료 하거나 취소 하면 끌기 작업이 끝날 때 무효화 컨텍스트를 반환 합니다.GetInvalidationContextForEndingInteractiveMovementOfItems – Returns the invalidation context at the end of a drag operation when the user either finishes the drag or cancels it.
  • GetInvalidationContextForInteractivelyMovingItems – 끌기 작업을 시작할 때 무효화 컨텍스트를 반환 합니다.GetInvalidationContextForInteractivelyMovingItems – Returns the invalidation context at the start of a drag operation.
  • GetLayoutAttributesForInteractivelyMovingItem – 항목을 끄는 동안 지정 된 항목의 레이아웃 특성을 가져옵니다.GetLayoutAttributesForInteractivelyMovingItem – Gets the Layout Attributes for a given item while dragging an item.
  • GetTargetIndexPathForInteractivelyMovingItem – 항목을 끌 때 지정 된 지점에 있는 항목의 indexPath 반환 합니다.GetTargetIndexPathForInteractivelyMovingItem – Returns the indexPath of the item that is at the given point when dragging an item.

UICollectionViewLayoutAttributesUICollectionViewLayoutAttributes

IOS 9의 UICollectionViewLayoutAttributes 클래스에 대 한 다음과 같이 변경 되거나 추가 되었습니다.The following changes or additions have been made to the UICollectionViewLayoutAttributes class in iOS 9:

  • CollisionBoundingPath – 끌기 작업 동안 두 항목의 충돌 경로를 반환 합니다.CollisionBoundingPath – Returns the collision path of two items during a drag operation.
  • CollisionBoundsType – 끌기 작업 중에 발생 한 충돌 유형 (UIDynamicItemCollisionBoundsType)을 반환 합니다.CollisionBoundsType – Returns the type of collision (as a UIDynamicItemCollisionBoundsType) that has occurred during a drag operation.

UICollectionViewLayoutInvalidationContextUICollectionViewLayoutInvalidationContext

IOS 9의 UICollectionViewLayoutInvalidationContext 클래스에 대 한 다음과 같이 변경 되거나 추가 되었습니다.The following changes or additions have been made to the UICollectionViewLayoutInvalidationContext class in iOS 9:

  • InteractiveMovementTarget – 끌기 작업의 대상 항목을 반환 합니다.InteractiveMovementTarget – Returns the target item of a drag operation.
  • PreviousIndexPathsForInteractivelyMovingItems – 순서를 변경 하기 위해 끌기 작업에 관련 된 다른 항목의 indexPaths 반환 합니다.PreviousIndexPathsForInteractivelyMovingItems – Returns the indexPaths of other items involved in a drag to reorder operation.
  • TargetIndexPathsForInteractivelyMovingItems – 순서 바꾸기 작업의 결과로 다시 정렬 될 항목의 indexPaths 반환 합니다.TargetIndexPathsForInteractivelyMovingItems – Returns the indexPaths of items that will be reordered as a result of a drag-to-reorder operation.

UICollectionViewSourceUICollectionViewSource

IOS 9의 UICollectionViewSource 클래스에 대 한 다음과 같이 변경 되거나 추가 되었습니다.The following changes or additions have been made to the UICollectionViewSource class in iOS 9:

  • CanMoveItem – 지정 된 항목을 끌어서 다시 정렬할 수 있는 경우 컬렉션 뷰에 알립니다.CanMoveItem – Informs the collection view if a given item can be drag reordered.
  • GetTargetContentOffset – 순서 재정리 작업을 통해 이동 하는 항목의 오프셋을 반환 합니다.GetTargetContentOffset – Returns the offsets of items that will be moved via a drag-to-reorder operation.
  • GetTargetIndexPathForMove – 순서를 바꾸기 작업을 수행 하는 동안 이동 하는 항목의 indexPath 반환 합니다.GetTargetIndexPathForMove – Returns the indexPath of an item that will be moved during a drag-to-reorder operation.
  • MoveItem – 목록에서 지정 된 항목의 순서를 이동 합니다.MoveItem – Moves the order of a given item in the list.

요약Summary

이 문서에서는 iOS 9의 컬렉션 보기에 대 한 변경 내용을 설명 하 고 Xamarin.ios에서 구현 하는 방법에 대해 설명 했습니다.This article has covered the changes to collection views in iOS 9 and described how to implement them in Xamarin.iOS. 컬렉션 뷰에서 간단한 순서 바꾸기 작업을 구현 하는 방법에 대해 설명 합니다. 끌어서 재주문과 함께 사용자 지정 제스처 인식기 사용 그리고 순서를 변경 하 여 사용자 지정 컬렉션 뷰 레이아웃에 영향을 주는 방법을 설명 합니다.It covered implementing a simple drag-to-reorder action in a collection view; using a custom Gesture Recognizer with drag-to-reorder; and how drag-to-reorder affects a custom collection view layout.