What is the adapter pattern
The adapter pattern belongs to one of the structural patterns. It is used very much and is very common. This paper learns the adapter pattern through a scroll diagram, code Swifty.
Adapter mode, focus on the adaptation of two words, Apple and the original approach is a little different, reflected in the UITableViewDelegate, UITableViewDataSource.
- Here’s an example that might seem far-fetched.
demand
Requirements are as follows:
- When you click on the top image, the tableView below displays the corresponding information
- The number of incoming images is not fixed
- A reload operation is required
Implemented using the adapter pattern
Define the protocol DataSource and Delegate
The data source
protocol HorizontalScrollerViewDataSource: class {
// Ask the data source how many views it wants to present inside the horizontal scroller
func numberOfViews(in horizontalScrollerView: HorizontalScrollerView) -> Int
// Ask the data source to return the view that should appear at <index>
func horizontalScrollerView(_ horizontalScrollerView: HorizontalScrollerView, viewAt index: Int) -> UIView
}
Copy the code
interaction
protocol HorizontalScrollerViewDelegate: class {
// inform the delegate that the view at <index> has been selected
func horizontalScrollerView(_ horizontalScrollerView: HorizontalScrollerView, didSelectViewAt index: Int)
}
Copy the code
The complete HorizontalScrollerView
class HorizontalScrollerView: UIView { weak var dataSource: HorizontalScrollerViewDataSource? weak var delegate: HorizontalScrollerViewDelegate? // 1 private enum ViewConstants { static let Padding: CGFloat = 10 static let Dimensions: CGFloat = 100 static let Offset: CGFloat = 100 } // 2 private let scroller = UIScrollView() // 3 private var contentViews = [UIView]() override init(frame: CGRect) { super.init(frame: frame) initializeScrollView() } required init? (coder aDecoder: NSCoder) { super.init(coder: aDecoder) initializeScrollView() } func initializeScrollView() { scroller.delegate = self //1 addSubview(scroller) //2 scroller.translatesAutoresizingMaskIntoConstraints = false //3 NSLayoutConstraint.activate([ scroller.leadingAnchor.constraint(equalTo: self.leadingAnchor), scroller.trailingAnchor.constraint(equalTo: self.trailingAnchor), scroller.topAnchor.constraint(equalTo: self.topAnchor), scroller.bottomAnchor.constraint(equalTo: self.bottomAnchor) ]) //4 let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(scrollerTapped(gesture:))) scroller.addGestureRecognizer(tapRecognizer) } func scrollToView(at index: Int, animated: Bool = true) { let centralView = contentViews[index] let targetCenter = centralView.center let targetOffsetX = targetCenter.x - (scroller.bounds.width / 2) scroller.setContentOffset(CGPoint(x: targetOffsetX, y: 0), animated: animated) } @objc func scrollerTapped(gesture: UITapGestureRecognizer) { let location = gesture.location(in: scroller) guard let index = contentViews.index(where: { $0.frame.contains(location)}) else { return } delegate?.horizontalScrollerView(self, didSelectViewAt: index) scrollToView(at: index) } func view(at index :Int) -> UIView { return contentViews[index] } func reload() { // 1 - Check if there is a data source, if not there is nothing to load. guard let dataSource = dataSource else { return } //2 - Remove the old content views contentViews.forEach { $0.removeFromSuperview() } // 3 - xValue is the starting point of each view inside the scroller var xValue = ViewConstants.Offset // 4 - Fetch and add the new views contentViews = (0.. <dataSource.numberOfViews(in: self)).map { index in // 5 - add a view at the right position xValue += ViewConstants.Padding let view = dataSource.horizontalScrollerView(self, viewAt: index) view.frame = CGRect(x: CGFloat(xValue), y: ViewConstants.Padding, width: ViewConstants.Dimensions, height: ViewConstants.Dimensions) scroller.addSubview(view) xValue += ViewConstants.Dimensions + ViewConstants.Padding // 6 - Store the view so we can reference it later return view } // 7 scroller.contentSize = CGSize(width: CGFloat(xValue + ViewConstants.Offset), height: frame.size.height) } private func centerCurrentView() { let centerRect = CGRect( origin: CGPoint(x: scroller.bounds.midX - ViewConstants.Padding, y: 0), size: CGSize(width: ViewConstants.Padding, height: bounds.height) ) guard let selectedIndex = contentViews.index(where: { $0.frame.intersects(centerRect) }) else { return } let centralView = contentViews[selectedIndex] let targetCenter = centralView.center let targetOffsetX = targetCenter.x - (scroller.bounds.width / 2) scroller.setContentOffset(CGPoint(x: targetOffsetX, y: 0), animated: true) delegate?.horizontalScrollerView(self, didSelectViewAt: selectedIndex) } } extension HorizontalScrollerView: UIScrollViewDelegate { func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if ! decelerate { centerCurrentView() } } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { centerCurrentView() } }Copy the code
External use
Set up agent and data refresh
horizontalScrollerView.dataSource = self
horizontalScrollerView.delegate = self
horizontalScrollerView.reload()
Copy the code
Scroll to a line
horizontalScrollerView.scrollToView(at: currentAlbumIndex, animated: false)
Copy the code
conclusion
Adaptation is a mapping, and the logic is as follows:
A --> adapter --> A'Copy the code
Traditional approach: In general, the adapter mode is: the interface provided by A does not meet THE requirements of B, and adapter C is used to convert the interface provided by A so that B can meet the requirements.
Apple’s approach: pass parameters into the adapter through the protocol, so as to get the desired results, omit C, B directly operate the interface of A, so as to achieve the purpose of adaptation.
If you were asked to do this requirement, how would you design it? Will the adapter pattern be used? Enter group solo if you have different opinions.
other
- Refer to the article
- The code download
This article is formatted using MDNICE