版本记录
版本号 | 时间 |
---|---|
V1.0 | 2019.08.13 星期二 |
前言
iOS中有关视图控件用户能看到的都在UIKit框架里面,用户交互也是通过UIKit进行的。感兴趣的参考上面几篇文章。
1. UIKit框架(一) —— UIKit动力学和移动效果(一)
2. UIKit框架(二) —— UIKit动力学和移动效果(二)
3. UIKit框架(三) —— UICollectionViewCell的扩张效果的实现(一)
4. UIKit框架(四) —— UICollectionViewCell的扩张效果的实现(二)
5. UIKit框架(五) —— 自定义控件:可重复使用的滑块(一)
6. UIKit框架(六) —— 自定义控件:可重复使用的滑块(二)
7. UIKit框架(七) —— 动态尺寸UITableViewCell的实现(一)
8. UIKit框架(八) —— 动态尺寸UITableViewCell的实现(二)
9. UIKit框架(九) —— UICollectionView的数据异步预加载(一)
10. UIKit框架(十) —— UICollectionView的数据异步预加载(二)
11. UIKit框架(十一) —— UICollectionView的重用、选择和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、选择和重排序(二)
13. UIKit框架(十三) —— 如何创建自己的侧滑式面板导航(一)
14. UIKit框架(十四) —— 如何创建自己的侧滑式面板导航(二)
15. UIKit框架(十五) —— 基于自定义UICollectionViewLayout布局的简单示例(一)
16. UIKit框架(十六) —— 基于自定义UICollectionViewLayout布局的简单示例(二)
17. UIKit框架(十七) —— 基于自定义UICollectionViewLayout布局的简单示例(三)
18. UIKit框架(十八) —— 基于CALayer属性的一种3D边栏动画的实现(一)
19. UIKit框架(十九) —— 基于CALayer属性的一种3D边栏动画的实现(二)
20. UIKit框架(二十) —— 基于UILabel跑马灯类似效果的实现(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定义viewController的转场和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定义viewController的转场和展示(二)
24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (一)
源码
1. Swift
首先看下工程组织结构
Geocache
首先看一下代码
1. Geocache.h
#import
//! Project version number for Geocache.
FOUNDATION_EXPORT double GeocacheVersionNumber;
//! Project version string for Geocache.
FOUNDATION_EXPORT const unsigned char GeocacheVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import
2. Geocache.swift
import Foundation
import MobileCoreServices
public let geocacheTypeId = "com.razeware.geocache"
enum EncodingError: Error {
case invalidData
}
public class Geocache: NSObject, Codable {
// MARK: - Enums
public enum Key {
public static let name = "name"
public static let summary = "summary"
public static let latitude = "latitude"
public static let longitude = "longitude"
public static let image = "imageName"
}
// MARK: - Properties
public var name: String
public var summary: String
public var latitude: Double
public var longitude: Double
public var image: Data?
// MARK: - Initialization
public required init(
name: String,
summary: String,
latitude: Double,
longitude: Double,
image: Data? = nil
) {
self.name = name
self.summary = summary
self.latitude = latitude
self.longitude = longitude
self.image = image
}
public required init(_ geocache: Geocache) {
self.name = geocache.name
self.summary = geocache.summary
self.latitude = geocache.latitude
self.longitude = geocache.longitude
self.image = geocache.image
super.init()
}
}
// MARK: - NSItemProviderWriting
extension Geocache: NSItemProviderWriting {
public static var writableTypeIdentifiersForItemProvider: [String] {
return [geocacheTypeId,
kUTTypePNG as String,
kUTTypePlainText as String]
}
public func loadData(
withTypeIdentifier typeIdentifier: String,
forItemProviderCompletionHandler completionHandler:
@escaping (Data?, Error?) -> Void) -> Progress? {
if typeIdentifier == kUTTypePNG as String {
if let image = image {
completionHandler(image, nil)
} else {
completionHandler(nil, nil)
}
} else if typeIdentifier == kUTTypePlainText as String {
completionHandler(name.data(using: .utf8), nil)
} else if typeIdentifier == geocacheTypeId {
do {
let archiver = NSKeyedArchiver(requiringSecureCoding: false)
try archiver.encodeEncodable(self, forKey: NSKeyedArchiveRootObjectKey)
archiver.finishEncoding()
let data = archiver.encodedData
completionHandler(data, nil)
} catch {
completionHandler(nil, nil)
}
}
return nil
}
}
// MARK: - NSItemProviderReading
extension Geocache: NSItemProviderReading {
public static var readableTypeIdentifiersForItemProvider: [String] {
return [geocacheTypeId,
kUTTypePlainText as String]
}
public static func object(withItemProviderData data: Data,
typeIdentifier: String) throws -> Self {
if typeIdentifier == kUTTypePlainText as String {
guard let name = String(data: data, encoding: .utf8) else {
throw EncodingError.invalidData
}
return self.init(name: name, summary: "Unknown", latitude: 0.0, longitude: 0.0)
} else if typeIdentifier == geocacheTypeId {
do {
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
guard let geocache =
try unarchiver.decodeTopLevelDecodable(
Geocache.self, forKey: NSKeyedArchiveRootObjectKey) else {
throw EncodingError.invalidData
}
return self.init(geocache)
} catch {
throw EncodingError.invalidData
}
} else {
throw EncodingError.invalidData
}
}
}
CacheEditor
首先看下sb中的内容
下面就是源码了
1. CacheDetailViewController.swift
import UIKit
import Geocache
import MapKit
class CacheDetailViewController: UIViewController {
// MARK: - Properties
@IBOutlet weak var cacheImageView: UIImageView!
@IBOutlet weak var cacheNameTextField: UITextField!
@IBOutlet weak var cacheSummaryTextView: UITextView!
@IBOutlet weak var mapView: MKMapView!
private var geocache = Geocache(
name: "Eiffel Tower",
summary: """
The Eiffel Tower is a wrought-iron lattice tower on the Champ de \
Mars in Paris, France. It is named after the engineer Gustave Eiffel, \
whose company designed and built the tower.
""",
latitude: 48.858370, longitude: 2.294481,
image: UIImage(named: "eiffel_tower")?.pngData()
)
// MARK: - Lifecycle methods
override func viewDidLoad() {
super.viewDidLoad()
view.addInteraction(UIDragInteraction(delegate: self))
view.addInteraction(UIDropInteraction(delegate: self))
configureView()
}
// MARK: - Actions
@IBAction func nameTextFieldChanged(_ sender: Any) {
if let name = cacheNameTextField.text {
geocache.name = name
}
}
}
// MARK: - Private methods
private extension CacheDetailViewController {
func configureView() {
if let image = geocache.image {
cacheImageView.image = UIImage(data: image)
} else {
cacheImageView.image = nil
}
cacheNameTextField.text = geocache.name
cacheSummaryTextView.text = geocache.summary
let coordinate = CLLocationCoordinate2D(latitude: geocache.latitude, longitude: geocache.longitude)
let mapRect = MKCoordinateRegion(
center: coordinate,
latitudinalMeters: 1000,
longitudinalMeters: 1000
)
mapView.setRegion(mapRect, animated: false)
}
func adjustViewBrightness(to alpha: CGFloat) {
cacheImageView.alpha = alpha
cacheNameTextField.alpha = alpha
cacheSummaryTextView.alpha = alpha
mapView.alpha = alpha
}
}
// MARK: - UITextViewDelegate
extension CacheDetailViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
if let summary = textView.text {
geocache.summary = summary
}
}
}
// MARK: - UIDragInteractionDelegate
extension CacheDetailViewController: UIDragInteractionDelegate {
func dragInteraction(_ interaction: UIDragInteraction,
itemsForBeginning session: UIDragSession) -> [UIDragItem] {
let itemProvider = NSItemProvider(object: geocache)
let dragItem = UIDragItem(itemProvider: itemProvider)
return [ dragItem ]
}
}
// MARK: - UIDropInteractionDelegate
extension CacheDetailViewController: UIDropInteractionDelegate {
func dropInteraction(_ interaction: UIDropInteraction,
canHandle session: UIDropSession) -> Bool {
return session.canLoadObjects(ofClass: Geocache.self)
}
func dropInteraction(_ interaction: UIDropInteraction,
sessionDidUpdate session: UIDropSession) -> UIDropProposal {
return UIDropProposal(operation: .copy)
}
func dropInteraction(_ interaction: UIDropInteraction,
performDrop session: UIDropSession) {
session.loadObjects(ofClass: Geocache.self) { items in
if let geocaches = items as? [Geocache],
let geocache = geocaches.first {
self.geocache = geocache
self.configureView()
}
}
}
}
CacheMaker
首先看下sb中的内容
接着就是源码了
1. CachesViewController.swift
import UIKit
import Geocache
class CachesViewController: UIViewController {
// MARK: - Properties
@IBOutlet weak var inProgressCollectionView: UICollectionView!
@IBOutlet weak var completedCollectionView: UICollectionView!
// Initial in-progress geocaches are loaded from a .plist
private lazy var inProgressDataSource: CachesDataSource = {
guard let path = Bundle.main.path(forResource: "Geocaches", ofType: "plist")
else {
print("Failed to read Geocaches.plist")
return CachesDataSource(geocaches: [])
}
let fileUrl = URL.init(fileURLWithPath: path)
guard let geocachesArray = NSArray(contentsOf: fileUrl) as? [[String: Any]]
else { return CachesDataSource(geocaches: []) }
let geocaches: [Geocache] = geocachesArray.compactMap({ (geocache) in
guard
let name = geocache[Geocache.Key.name] as? String,
let summary = geocache[Geocache.Key.summary] as? String,
let latitude = geocache[Geocache.Key.latitude] as? Double,
let longitude = geocache[Geocache.Key.longitude] as? Double,
let imageName = geocache[Geocache.Key.image] as? String,
let image = UIImage(named: imageName)?.pngData()
else {
return nil
}
return Geocache(
name: name, summary: summary,
latitude: latitude, longitude: longitude, image: image)
})
return CachesDataSource(geocaches: geocaches)
}()
// Initial completed geocaches are empty
private var completedDataSource = CachesDataSource(geocaches: [])
// MARK: - Lifecycle methods
override func viewDidLoad() {
super.viewDidLoad()
for collectionView in [inProgressCollectionView, completedCollectionView] {
if let collectionView = collectionView {
collectionView.dataSource = dataSourceForCollectionView(collectionView)
collectionView.delegate = self
collectionView.dragDelegate = self
collectionView.dropDelegate = self
}
}
}
}
// MARK: - Private methods
private extension CachesViewController {
func dataSourceForCollectionView(
_ collectionView: UICollectionView
) -> CachesDataSource {
if collectionView == inProgressCollectionView {
return inProgressDataSource
} else {
return completedDataSource
}
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension CachesViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width, height: 100)
}
}
// MARK: - UICollectionViewDragDelegate
extension CachesViewController: UICollectionViewDragDelegate {
func collectionView(_ collectionView: UICollectionView,
itemsForBeginning session: UIDragSession,
at indexPath: IndexPath) -> [UIDragItem] {
let dataSource = dataSourceForCollectionView(collectionView)
let dragCoordinator = CacheDragCoordinator(sourceIndexPath: indexPath)
session.localContext = dragCoordinator
return dataSource.dragItems(for: indexPath)
}
func collectionView(_ collectionView: UICollectionView,
dragSessionDidEnd session: UIDragSession) {
guard
let dragCoordinator = session.localContext as? CacheDragCoordinator,
dragCoordinator.dragCompleted == true,
dragCoordinator.isReordering == false
else {
return
}
let dataSource = dataSourceForCollectionView(collectionView)
let sourceIndexPath = dragCoordinator.sourceIndexPath
collectionView.performBatchUpdates({
dataSource.deleteGeocache(at: sourceIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
})
}
}
// MARK: - UICollectionViewDropDelegate
extension CachesViewController: UICollectionViewDropDelegate {
func collectionView(_ collectionView: UICollectionView,
performDropWith coordinator: UICollectionViewDropCoordinator) {
// Get the datasource for this collection view
let dataSource = dataSourceForCollectionView(collectionView)
let destinationIndexPath: IndexPath
if let indexPath = coordinator.destinationIndexPath {
destinationIndexPath = indexPath
} else {
// Drop first item at the end of the collection view
destinationIndexPath =
IndexPath(item: collectionView.numberOfItems(inSection: 0), section: 0)
}
let item = coordinator.items[0]
switch coordinator.proposal.operation {
case .move:
guard let dragCoordinator =
coordinator.session.localDragSession?.localContext as? CacheDragCoordinator
else { return }
// Save information to calculate reordering
if let sourceIndexPath = item.sourceIndexPath {
print("Moving within the same collection view...")
dragCoordinator.isReordering = true
// Update datasource and collection view
collectionView.performBatchUpdates({
dataSource.moveGeocache(at: sourceIndexPath.item, to: destinationIndexPath.item)
collectionView.deleteItems(at: [sourceIndexPath])
collectionView.insertItems(at: [destinationIndexPath])
})
} else {
print("Moving between collection views...")
dragCoordinator.isReordering = false
// Update datasource and collection view
if let geocache = item.dragItem.localObject as? Geocache {
collectionView.performBatchUpdates({
dataSource.addGeocache(geocache, at: destinationIndexPath.item)
collectionView.insertItems(at: [destinationIndexPath])
})
}
}
// Set flag to indicate drag completed
dragCoordinator.dragCompleted = true
coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
case .copy:
print("Copying from different app...")
// Set up the placeholder
let context = coordinator.drop(item.dragItem, to: makePlaceholder(destinationIndexPath))
let itemProvider = item.dragItem.itemProvider
itemProvider.loadObject(ofClass: Geocache.self) { geocache, _ in
if let geocache = geocache as? Geocache {
DispatchQueue.main.async {
context.commitInsertion(dataSourceUpdates: {_ in
dataSource.addGeocache(geocache, at: destinationIndexPath.item)
})
}
}
}
default:
return
}
}
func makePlaceholder(_ destinationIndexPath: IndexPath) -> UICollectionViewDropPlaceholder {
let placeholder = UICollectionViewDropPlaceholder(
insertionIndexPath: destinationIndexPath, reuseIdentifier: "CacheCell")
placeholder.cellUpdateHandler = { cell in
if let cell = cell as? CacheCell {
cell.cacheNameLabel.text = "Loading..."
cell.cacheSummaryLabel.text = ""
cell.cacheImageView.image = nil
}
}
return placeholder
}
func collectionView(
_ collectionView: UICollectionView,
dropSessionDidUpdate session: UIDropSession,
withDestinationIndexPath destinationIndexPath: IndexPath?
) -> UICollectionViewDropProposal {
guard session.localDragSession != nil else {
return UICollectionViewDropProposal(
operation: .copy,
intent: .insertAtDestinationIndexPath)
}
guard session.items.count == 1 else {
return UICollectionViewDropProposal(operation: .cancel)
}
return UICollectionViewDropProposal(
operation: .move,
intent: .insertAtDestinationIndexPath)
}
}
2. CacheCell.swift
import UIKit
class CacheCell: UICollectionViewCell {
@IBOutlet weak var cacheImageView: UIImageView!
@IBOutlet weak var cacheNameLabel: UILabel!
@IBOutlet weak var cacheSummaryLabel: UILabel!
}
3. CachesDataSource.swift
import UIKit
import Geocache
class CachesDataSource: NSObject, UICollectionViewDataSource {
// MARK: - Properties
private var geocaches: [Geocache]
// MARK: - Initialization
init(geocaches: [Geocache]) {
self.geocaches = geocaches
super.init()
}
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return geocaches.count
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CacheCell",
for: indexPath)
if let cell = cell as? CacheCell {
let geocache = geocaches[indexPath.item]
if let image = geocache.image {
cell.cacheImageView.image = UIImage(data: image)
} else {
cell.cacheImageView.image = nil
}
cell.cacheNameLabel.text = geocache.name
cell.cacheSummaryLabel.text = geocache.summary
}
return cell
}
}
// MARK: - Helper methods
extension CachesDataSource {
func dragItems(for indexPath: IndexPath) -> [UIDragItem] {
let geocache = geocaches[indexPath.item]
let itemProvider = NSItemProvider(object: geocache)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = geocache
return [dragItem]
}
func addGeocache(_ newGeocache: Geocache, at index: Int) {
geocaches.insert(newGeocache, at: index)
}
func moveGeocache(at sourceIndex: Int, to destinationIndex: Int) {
guard sourceIndex != destinationIndex else { return }
let geocache = geocaches[sourceIndex]
geocaches.remove(at: sourceIndex)
geocaches.insert(geocache, at: destinationIndex)
}
func deleteGeocache(at index: Int) {
geocaches.remove(at: index)
}
}
4. CacheDragCoordinator.swift
import Foundation
class CacheDragCoordinator {
// MARK: - Properties
let sourceIndexPath: IndexPath
var dragCompleted = false
var isReordering = false
// MARK: - Initialization
init(sourceIndexPath: IndexPath) {
self.sourceIndexPath = sourceIndexPath
}
}
5. RotatingFlowLayout.swift
import UIKit
class RotatingFlowLayout: UICollectionViewFlowLayout {
override func invalidationContext(
forBoundsChange newBounds: CGRect
) -> UICollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forBoundsChange: newBounds)
guard
let collectionView = collectionView,
collectionView.bounds.size != newBounds.size
else {
return context
}
(context as? UICollectionViewFlowLayoutInvalidationContext)?.invalidateFlowLayoutDelegateMetrics = true
return context
}
}
后记
本篇主要讲述了基于UICollectionViews和Drag-Drop在两个APP间的使用示例,感兴趣的给个赞或者关注~~~