Adobe xD to SWIFT

by DaoNM2
273 views

Xin chào! Bài viết này mình muốn chia sẻ cho mọi người về một số cách để code SWIFT giống với Adobe xD và hạn chế phần nào việc bị bắt bug UI không đáng.

Trong nỗi trăn trở ở mỗi dự án mobile có hàng trăm, hàng ngàn các bug UI được log. Tự nhiên mình lại nghĩ phải làm việc gì đó để giúp cho anh em code UI ngon hơn, đỡ tạo ra bug UI hơn. Vì vậy mình đã viết bài viết này hi vọng sẽ giúp anh em được phần nào trong việc tránh dính phải những bug UI.

Khi làm việc với xD các anh em thường bỏ qua các chỉ số của xD mà hay tự thực hiện code để nhìn sao cho giống UI nhất có thể, nếu không hiểu rõ bản chất nó khiến cho anh em mất khá nhiều thời gian để có thể làm giống được với file thiết kế cụ thể ở đây là file Adobe xD.

Drop shadow

Để giúp mọi người làm việc dễ dàng hơn nên mình đã tạo ra một hàm trong CALayer để giúp anh em đổ bóng bao chuẩn, bao giống xD :v Việc của anh em là lấy chỉ số ở xD và truyền vào func để setup là xong.

extension CALayer {
    
    /// make shadow like Adobe xD, all prameters using same value with Adobe xD
    /// - Parameters:
    ///   - color: shadow color
    ///   - opacity: alpha of shadow color (0-100)
    ///   - x: x
    ///   - y: y
    ///   - b: shadow radius
    func dropShadowLikeXD(color: UIColor = .black,
                          opacity: Int = 50,
                          x: CGFloat = 0,
                          y: CGFloat = 3,
                          b: CGFloat = 6) {
        masksToBounds = false
        shadowColor = color.cgColor
        shadowOpacity = Float(opacity) / 100.0
        shadowOffset = CGSize(width: x, height: y)
        shadowRadius = b / 2.0
        shadowPath = nil
    }
}

Để anh em dễ hiểu hơn thì mình xin giải thích như sau:
– color: đây là shadow color, nó là màu shadow trên xD, cái này khá đơn giản mọi người chỉ cần lấy màu trên xD và fill vào là xong
– opacity: đây là độ trong suốt của shadow, trên Adobe xD không có thuộc tính này mà giá trị này sẽ là Opacity của shadow color
– x: độ lệch của shadow so với view, tính từ trái qua phải. x > 0 thì shadow lệch qua phải và ngược lại
– y: độ lệch của shadow so với view, tính từ trên xuống dưới, x > thì shadow lệch xuống dưới và ngược lại.
– b: là thuộc tính blur trên xD, nhưng trong Swift không có thuộc tính này, mà chỉ có shadowRadius nó là bán kính của shadow, và nó bằng 1/2 blur trên xD.
– masksToBounds: thuộc tính này bằng true sẽ không thể tạo được shadow vì nó sẽ cắt mất view shadow đi. vì trong hàm mình đã set lại giá trị này bằng false.

Lưu ý: Để vừa đổ bóng được kết hợp với bo góc chúng ta cần thực hiện bo góc trước khi gọi hàm dropShadowLikeXD()

Border

Border trong Adobe xD cho phép custom khá nhiều thuộc tính, tuy nhiên mấy ông Dev tạo ra CALayer của Apple lại chỉ cho set mỗi 2 thuộc tính là borderWidth và borderColor. Vì vậy để làm giống Adobe xD chúng ta sẽ mất công hơn 1 chút. Cụ thể chúng ta sẽ cần thêm 1 enum và một func trong extension của UIView như sau:

extension UIView {
    enum BorderStrokeStyle {
        case inner 
        case outer
        case center
    }
    
    func borderLikeXD(size: CGFloat = 1,
                      color: UIColor = .black,
                      dash: CGFloat = 0,
                      cap: CAShapeLayerLineCap = .butt,
                      join: CAShapeLayerLineJoin = .miter,
                      stroke: BorderStrokeStyle = .inner) {
        if dash <= 0 {
            layer.borderColor = color.cgColor
            layer.borderWidth = size
        } else {
            let newShapeLayer  = CAShapeLayer()
            newShapeLayer.strokeColor = color.cgColor
            newShapeLayer.lineWidth = size
            newShapeLayer.lineDashPattern = [NSNumber(value: dash), NSNumber(value: dash)]
            newShapeLayer.frame = self.bounds
            newShapeLayer.fillColor = nil
            newShapeLayer.lineCap = cap
            newShapeLayer.lineJoin = join
            switch stroke {
            case .inner:
                let innerBounds = CGRect(x: bounds.origin.x + size / 2, y: bounds.origin.y + size / 2, width: bounds.size.width - size, height: bounds.size.height - size)
                newShapeLayer.path = UIBezierPath(roundedRect: innerBounds, cornerRadius: layer.cornerRadius).cgPath
            case .outer:
                let outerBounds = CGRect(x: bounds.origin.x - size / 2, y: bounds.origin.y - size / 2, width: bounds.size.width + size, height: bounds.size.height + size)
                newShapeLayer.path = UIBezierPath(roundedRect: outerBounds, cornerRadius: layer.cornerRadius).cgPath
            case .center:
                newShapeLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath
            }
            
            self.layer.addSublayer(newShapeLayer)
            
        }
    }
}

Lưu ý: Do hàm này thực hiện thêm mới sublayer nên mọi người không nên gọi nó thực hiện ở func có thể gọi nhiều lần như: viewWillAppear(), viewDidAppear() …

Line spacing

Do định nghĩa về line spacing của Apple và Adobe xD khác nhau nên chúng ta không thể sử dụng cùng chỉ số được, vì vậy chúng ta cần tạo ra một phương thức để sửa lại công thức sao cho khớp với Adobe xD.

Adobe xD định nghĩa line spacing: là khoảng cách từ Top của dòng trên so với Top của dòng dưới liền kề.

Apple định nghĩa line spacing: là khoảng cách giữa Bot của dòng trên so với Top của dòng dưới liền kề.

Line spacing

Chúng ta có thể nhận ra sự chênh lệch giá trị line spacing của Apple so với Adobe chính là chiều cao của 1 dòng. Vậy nên mình có tạo ra một func giúp mọi người set lại giá trị line spacing giống xD mà không phải đau đầu tính toán nữa.

extension UILabel {
    /// Set line spacing for label
    ///
    /// - Parameter lineSpacing: Line spacing
    func setLineSpacing(_ lineSpacing: CGFloat) {
        // Check label text empty
        guard let labelText: String = self.text,
              let font = self.font else {
            return
        }
        let constraintRect: CGSize = CGSize(width: self.bounds.width, height: .greatestFiniteMagnitude)
        let boundingBox: CGRect = "Ok".boundingRect(with: constraintRect,
                                                    options: .usesLineFragmentOrigin,
                                                    attributes: [NSAttributedString.Key.font: font],
                                                    context: nil)
        let heightLabel: CGFloat = ceil(boundingBox.height)
        let paragraphStyle: NSMutableParagraphStyle = NSMutableParagraphStyle()
        // line spacing on xD - height of one line
        paragraphStyle.lineSpacing = lineSpacing - heightLabel
        
        let attributedString: NSMutableAttributedString
        if let labelattributedText: NSAttributedString = self.attributedText {
            attributedString = NSMutableAttributedString(attributedString: labelattributedText)
        } else {
            attributedString = NSMutableAttributedString(string: labelText)
        }
        
        // Line spacing attribute
        attributedString.addAttribute(NSAttributedString.Key.paragraphStyle,
                                      value: paragraphStyle,
                                      range: NSRange(location: 0,
                                                     length: attributedString.length))
        
        self.attributedText = attributedString
    }
}

Trên đây là những gì mình muốn chia sẻ lại cho mọi người, hi vọng nó sẽ giúp được mọi người phần nào trong công việc. Nếu mọi người có câu hỏi hay thắc mắc gì có thể đặt câu hỏi ở dưới comment mình sẽ cố gắng giải đáp những thắc mắc của mọi người.

Xin cảm ơn mọi người đã đọc bài viết của mình!

1 comment

Người buôn gió August 17, 2022 - 3:49 PM

Bài viết thật bổ ích. Xin cảm ơn tác giả ^^

Reply

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

You may also like

%d bloggers like this: