iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0
自我挑戰組

iOS Junior的菜雞之路系列 第 17

TinderCard Slide 研究 - 2

  • 分享至 

  • xImage
  •  

昨天寫下了SlideViewController是怎麼讓他資料刪除的
今天就會是卡片的部分

TinderCard

// 閾值如果超過閾值就進行喜歡不喜歡的判斷,否則卡片回復原位
let theresoldMargin = (UIScreen.main.bounds.size.width/2) * 0.75
let stength : CGFloat = 4
let range : CGFloat = 0.90


// 用來通知上面的ViewController卡片做了什麼事情
protocol TinderCardDelegate: NSObjectProtocol {
    func didSelectCard(card: TinderCard)
    func cardGoesRight(card: TinderCard)
    func cardGoesLeft(card: TinderCard)
    func currentCardStatus(card: TinderCard, distance: CGFloat)
    func fallbackCard(card: TinderCard)
}

class TinderCard: UIView {
    @IBOutlet weak var statusImageView: UIImageView!
    @IBOutlet weak var overlayImageView: UIImageView! {
        didSet {
            overlayImageView.contentMode = .scaleAspectFill
        }
    }
    @IBOutlet weak var overlay: UIImageView! {
        didSet {
            overlay.contentMode = .scaleAspectFill
        }
    }


    var index: Int!
    weak var delegate: TinderCardDelegate?

    var xCenter: CGFloat = 0.0
    var yCenter: CGFloat = 0.0
    var originalPoint = CGPoint.zero

    var isLiked = false
    var model : Any?


    override func awakeFromNib() {
        super.awakeFromNib()

    }

    static func initXib() -> TinderCard {
        let v = UINib(nibName: "TinderCard", bundle: nil).instantiate(withOwner: nil, options: nil).first as! TinderCard
        return v
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupView()
    }

寫套件的大大使用的是程式直接寫程式碼來處理,但我覺得那樣太麻煩,索性改成使用Xib
也符合這個鐵人賽前面的文章

因為使用的cell的Custom Class,所以不能把initXib放在overrride init內部,否則會造成無盡迴圈,一直Call Function

因為是今天做的更動,所以昨天的程式碼也會有所更動(當然這篇Po上去的時候都是已經改好的狀態)

    /*
     * Initializing View
     */
    func setupView() {

        layer.cornerRadius = bounds.width/20
        layer.shadowRadius = 3
        layer.shadowOpacity = 0.4
        layer.shadowOffset = CGSize(width: 0.5, height: 3)
        layer.shadowColor = UIColor.darkGray.cgColor
        clipsToBounds = true
        backgroundColor = .white
        originalPoint = center

        let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.beingDragged))
        panGestureRecognizer.delegate = self
        addGestureRecognizer(panGestureRecognizer)
    }


    // 因為我是使用Xib直接畫,而非像作者那樣在外圈導入
    func addContentView(element: String){
        let url = URL(string: element)!
        self.overlay.kf.setImage(with: url)
    }

    /*
     * Card goes right method
     */
    func cardGoesRight() {

        delegate?.cardGoesRight(card: self)
        let finishPoint = CGPoint(x: frame.size.width*2, y: 2 * yCenter + originalPoint.y)
        self.removeFromSuperview()
        isLiked = true
    }

    /*
     * Card goes left method
     */
    func cardGoesLeft() {

        delegate?.cardGoesLeft(card: self)
        let finishPoint = CGPoint(x: -frame.size.width*2, y: 2 * yCenter + originalPoint.y)
        self.removeFromSuperview()
        isLiked = false
    }

    /*
     * Setting up initial status for imageviews
     */
    fileprivate func setInitialLayoutStatus(isleft:Bool){

        statusImageView.alpha = 0.5
        overlayImageView.alpha = 0.5

        statusImageView.image = makeImage(name: isleft ?  "ic_skip" : "ic_like")
        overlayImageView.image = makeImage(name: isleft ?  "overlay_skip" : "overlay_like")
    }


    /*
     * Acessing image from bundle
     */
    fileprivate func makeImage(name: String) -> UIImage? {

        let image = UIImage(named: name)
        return image
    }

    /*
     * Animation with center point
     */
    fileprivate func animateCard(to center:CGPoint, angle:CGFloat = 0, alpha:CGFloat = 0){

        self.center = center
        self.transform = CGAffineTransform(rotationAngle: angle)
        statusImageView.alpha = alpha
        overlayImageView.alpha = alpha
    }

// MARK: UIGestureRecognizerDelegate Methods
extension TinderCard: UIGestureRecognizerDelegate {

    /*
     * Gesture methods
     */
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }

    /*
     * Gesture methods
     */
    @objc fileprivate func beingDragged(_ gestureRecognizer: UIPanGestureRecognizer) {

        xCenter = gestureRecognizer.translation(in: self).x
        yCenter = gestureRecognizer.translation(in: self).y
        switch gestureRecognizer.state {
        // Keep swiping
        case .began:
            originalPoint = self.center;
            self.delegate?.didSelectCard(card: self)
            break;
        //in the middle of a swipe
        case .changed:
            let rotationStrength = min(xCenter / UIScreen.main.bounds.size.width, 1)
            let rotationAngel = .pi/8 * rotationStrength
            let scale = max(1 - abs(rotationStrength) / stength, range)
            center = CGPoint(x: originalPoint.x + xCenter, y: originalPoint.y + yCenter)

            print("Center",center)
            print("OverlayCenter",overlay.center)
            print("StatusImageView.Center",statusImageView.center)
            print("OverlayImageView",overlayImageView.center)
            let transforms = CGAffineTransform(rotationAngle: rotationAngel)
            let scaleTransform: CGAffineTransform = transforms.scaledBy(x: scale, y: scale)
            self.transform = scaleTransform
            updateOverlay(xCenter)
            break;

        // swipe ended
        case .ended:
            afterSwipeAction()
            break;

        case .possible:break
        case .cancelled:break
        case .failed:break
        @unknown default:
            fatalError()
        }
    }

    /*
     * Tinder Card swipe action
     */
    // 這邊是滑動結束後處理喜歡不喜歡,看有沒有超過閾值
    fileprivate func afterSwipeAction() {

        if xCenter > theresoldMargin {
            cardGoesRight()
        }
        else if xCenter < -theresoldMargin {
            cardGoesLeft()
        }
        else {
            self.delegate?.fallbackCard(card: self)
            UIView.animate(withDuration: 0.3, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 1.0, options: [], animations: {
               self.center = self.originalPoint
                self.transform = CGAffineTransform(rotationAngle: 0)
                self.statusImageView.alpha = 0
                self.overlayImageView.alpha = 0
            })
        }
    }

    /*
     * Updating overlay methods
     */
    // 更新Mark圖層,可以知道是綠色的還是紅色的
    fileprivate func updateOverlay(_ distance: CGFloat) {

        statusImageView.image = makeImage(name:  distance > 0 ? "ic_like" : "ic_skip")
        overlayImageView.image = makeImage(name:  distance > 0 ? "overlay_like" : "overlay_skip")
        statusImageView.alpha = min(abs(distance) / 100, 0.8)
        overlayImageView.alpha = min(abs(distance) / 100, 0.8)
        delegate?.currentCardStatus(card: self, distance: distance)
    }
}

參考網址:
https://github.com/nickypatson/TinderSwipeView/blob/master/TinderSwipeView/Classes/TinderCard.swift
https://dev.to/ingun37/file-s-owner-is-not-for-uiview-3n9g

不過目前滑動動畫還是有點小Bug
就會滑動到一定位置之後就會開始旋轉,但我還未Debug出來


坑: 動畫好難啊,有看沒有懂,尤其又是搭配手勢


上一篇
TinderCard Slide 研究 - 1
下一篇
顯示你所愛的,以及愛你的
系列文
iOS Junior的菜雞之路30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言