image.png

상품을 작성할 때, text 길이에 따라서 TextField와 layer의 width를 동적으로 변화해야 한다.

따라서 TextField를 커스텀하기로 했다.

동적으로 변하는 text길이를 통해 textField의 width 오토레이아웃 설정

// in TextField
private func bind() {
    NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: self)
      .compactMap { ($0.object as? UITextField)?.text }
      .map { text in
        let textWidth = text.size(withAttributes: [.font: self.font!]).width
        return max(100, textWidth + 40)
      }
      .sink { [weak self] newWidth in
        self?.updateLayout(with: newWidth)
      }
      .store(in: &self.subscriptions)
  }
  
  private func updateLayout(with newWidth: CGFloat) {
    self.widthConstraint?.constant = newWidth
  }

NotificationCenter를 통해 textDidChangeNotification가 감지될 때마다 textString을 받는다. textString은 font에 맞게 width를 계산한다. UX를 위해 좌우 합산 20 + 20 = 40 의 여백을 만들고, 최소 width가 100을 넘기도록 max를 통해 newWidth를 만든다. 오토레이아웃을 사용하고 있기 때문에 widthConstraint값을 설정한다.

textField width를 사용해서 layer width 변경

textField의 width가 변경되었으니, layer의 width를 변경해주어야 한다.


// in TextField
private var previousWidth: CGFloat = 0
private let bezierPath = UIBezierPath()
private let dashLayer = CAShapeLayer()

override func layoutSubviews() {
    super.layoutSubviews()
    
    let shouldUpdateDashedBorder = self.previousWidth != bounds.width
    
    if shouldUpdateDashedBorder {
      self.previousWidth = bounds.width
      self.updateDashedBorder()
    }
}
  
  private func updateDashedBorder() {
    bezierPath.removeAllPoints()
    
    bezierPath.move(to: CGPoint(x: .zero, y: bounds.height - self.borderHeight))
    bezierPath.addLine(to: CGPoint(x: bounds.width, y: bounds.height - self.borderHeight))
    dashLayer.path = bezierPath.cgPath
  }
  
  
  private func setupStyle(borderColor: UIColor) {
    self.dashLayer.strokeColor = borderColor.cgColor
    self.dashLayer.frame.size.height = 1
    self.dashLayer.strokeColor = UIColor.black.cgColor
    self.dashLayer.lineWidth = 1
    self.dashLayer.fillColor = nil
    self.dashLayer.lineDashPattern = [2, 2]
    
    self.backgroundColor = .black.withAlphaComponent(0.1) // to erase
  }

layoutSubviews

textField의 widthConstraints를 변경하고 오토레이아웃이 설정되면 layoutSubviews가 호출되므로, bounds를 통해 TextField의 최신 width를 알 수 있다. 따라서 layoutSubviews에서 TextField의 최신 width를 통해 layer의 width를 변경한다.

CAShapeLayer

CAShapeLayer를 사용해서 dash style을 설정한다.