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