12강 정리
학습 내용
UISplitViewController
splitViewController에는 preferredDisplayMode가 있습니다.
preferredDisplayMode를 바꿔서 overlap, beside, displace등을 설정해줄 수 있습니다.
ScrollView의 Content Size
코드는 contentSize 변수에 크기를 넣어주기만 하면 됩니다.
하지만 스토리보드에서는 인터페이스 빌더가 서브뷰의 제약조건을 보고 contentSize를 자동으로 만들어줍니다.
따라서 스토리보드에서 작업 시 반드시 서브 뷰가 있어야하며, 또한 content size를 추측할 수 있게 위,아래,양 옆 모두 제약 조건이 있어야하합니다.(그렇지 않을 경우 스토리보드 상에서 오토레이아웃이 제대로 작동하지 않는 모습을 볼 수 있습니다.)
레이아웃을 scrollView frame에 달거나 contentView에 달 수 있는 frame layout guide와 content layout guide가 있습니다.
UITable/CollectionViewDrag/DropDelegate
드래그 앤 드랍 기능을 테이블뷰, 컬렉션뷰에서 사용하려면 해당 프로토콜을 채택해야합니다.
delegate를 설정해 준 뒤, 필수 델리게이션 메소드를 구현하면 드래그 앤 드랍 기능을 사용할 수 있습니다.
다만, 한 가지 다른 점은 드랍 델리게이션 메소드인 seesionDidUpdate에서 UIDropProposal이 .cancel, .move, .copy 중에서 무엇인지만 물어보았다면, 테이블뷰/컬렉션뷰는 드랍 아이템의 의도가 무엇인지 추가로 알려주어야합니다. (스크롤 뷰 안에 셀 뷰가 있기 때문에 어느 뷰에 작용하는지 알려줘야합니다.)
따라서 UICollectionViewDropProposal(operation: , intent: ) 처럼 intent가 무엇인지 명시해야합니다.
드랍을 할 때는 1. 컬렉션 뷰에 셀을 추가하는 건지, 2. 컬렉션 뷰의 셀 안에 내용을 추가하는 건지에 따라서 1. insertAtDestionationIndexPath, 2. insertIntoDestionationIndexPath로 명시하면 됩니다.
Local Object
드래그 앤 드랍이 단일 앱 내부에서 발생한다면 itemProvider를 이용할 수도 있지만, dragItem.localObject에 Any 타입으로 넘겨줄 수도 있습니다.(단일 앱에서 효율적으로 동작합니다. 또한 .move도 사용가능합니다.)
드랍 단계에서 단일 앱에서 드랍이 됬는지 파악한 후, 맞다면 loadObject가 아닌 dragItem.localObject에서 꺼내와 사용하면 됩니다.(타입 캐스팅 역시 필요합니다.)
추가로 drag 함수에 있는 session에 localObject라는 변수를 이용하면, 내가 원하는 커스텀 데이터를 붙여서 drop 단계에서 받을 수 있습니다.
session.localObject에 값을 넣어주면, 후에 drop 함수에서 sesson.localDragSession?.localContext을 통해 값을 꺼내올 수 있으며, 주의할 점은 Any 타입이기 때문에 타입 캐스팅을 해주어야합니다.
DropCoordinator
테이블뷰/컬렉션뷰는 performDrop을 session으로 받는 게 아닌, dropCoordinator로 받습니다.
그 이유는 테이블뷰/컬렉션뷰는 드랍이 되는 위치도 중요하기 때문에, 위치에 따른 인덱스를 계산해서 coordinator.destinationIndexPath으로 알려줍니다.
또한, coordinator.items는 [UICollectionViewDropItem]이며, 추가적으로 indexPath를 통해서 얻어온 드래그인 경우 sourceIndexPath를 알 수 있습니다.
일반적인 session에 접근하려면, coordinator.session을 사용하면 됩니다.
드랍 애니메이션이 없기 때문에 애니메이션을 실행하려면 coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)의 함수를 호출해야합니다.
드랍은 단일 앱이 아니라면 기본적으로 데이터를 비동기적으로 가져오기에 컬렉션 뷰와 싱크가 맞지 않는 문제가 발생합니다.
따라서, 컬렉션 뷰에 placeholder cell을 추가해주고, 데이터를 다 받으면 원래의 셀로 바꿔주는 작업을 수행합니다.
1. let placeholderContext = coordinator.drop(item.dragItem, to: UICollectionViewDropPlaceholder(insertionIndexPath: destinationIndexPath, reuseIdentifier: "DropPlaceHolderCell"))을 통해 애니메이션과 placeholder cell을 컬렉션 뷰에 추가해줍니다.
이 때, 아직 모델에 데이터를 갱신하지 않은 상태라 컬렉션뷰의 셀 갯수와 모델의 데이터 갯수가 다른 문제가 생깁니다.
그러나 컬렉션 뷰가 자동적으로 placeholder는 갱신하되, 실제 row의 갯수에 포함시키지 않아 안전합니다.(placeholder는 다른 cellForItemAt 함수를 사용하는 것 같습니다.)
2. placeholderContext.commitInsertion(dataSourceUpdates: (IndexPath) -> Void) -> Bool 를 통해 dataSource를 업데이트해줍니다.
기존에 placeholder의 위치로 넣었던 indexPath는 이전의 작업으로 인해 위치가 바뀌었을 가능성이 있으므로, 컬렉션뷰가 자동으로 계산해준 인자 IndexPath를 사용하면 됩니다.
dataSource를 갱신하면 자동으로 placeholder의 셀을 delegate 메소드를 통해 갱신해줍니다.
또는 실패했을 경우, placeholderContext.deletePlaceholder()로 placeholder cell을 지웁니다.
performBatchUpdates
테이블뷰, 컬렉션뷰는 insert, move, delete 함수를 통해 셀을 변경합니다.
이 때, 여러 연산을 하는 도중에 인덱스가 지속적으로 바뀌는 문제와 여러 연산을 각각 단일 애니메이션으로 수행한다는 단점이 있습니다.
위 함수에 클로저 인자로 여러 연산들을 넣어서 수행하면, 그룹 단위로 클로저를 실행해주어 위와 같은 문제를 해결해줍니다.
UITextField
iOS에서는 키보드와 관련된 객체가 없습니다.
단지 UITextField를 first responder로 만듦으로해서 해당 필드를 터치하면 자동적으로 나타나게 합니다.
becomeFirstResponder와 resignFirstResponder를 적절하게 사용하면 우리가 원하는 결과를 얻을 수 있습니다.
UITextField는 UIControl이기 때문에 target/action을 설정할 수 있으며, 이는 어떤것이 바뀌었을때 당신에게 알려주게 됩니다.
Button과 같이 UIControlEvents들이 있으며, 스토리보드에서 오른쪽 마우스 클릭을 통해 확인할 수 있습니다.
추가로 border, left/right view, clear button 같은 기능들도 있습니다.
UITextInputTraits
키보드의 설정을 바꾸고 싶다면, 키보드를 호출한 객체에게 속성 값을 설정해야합니다.
객체는 UITextInputTraits 프로토콜을 따라야합니다.
여기에 키보드 위에 작은 뷰를 넣을 수 있는 var inputAccessortView: UIView가 있습니다.(보통 툴바를 많이 사용합니다.)
NotificationCenter
키보드가 나타나면 뷰가 가려지므로, NotificationCenter.default.addObserver에 뷰를 조절하는 함수를 등록해 이에 대처할 수 있습니다.
테이블뷰는 자동적으로 textField가 가려지지 않도록 자동적으로 스크롤을 조정해주기도 합니다.