[스탠포드 iOS] 6강 멀티터치

6강 목표

  • Gestures

Context, UIBezierPath

  • Storyboard에 UIView를 하나 올리고 PlayingCardView 클래스와 연결시켜준다.

  • 시스템이 해당 View를 drawing할 때 해당 코드 draw()를 사용한다.
  • Context를 이용할 때 fillPath()가 나타나지 않고 UIBezierPath 구조체를 활용하면 문제를 해결할 수 있다.
  • 가로로 회전했을 때 타원형으로 보이는 것을 볼 수 있다.

  • Content Mode에서 Scale To Fill에서 Redraw로 변경으로 문제 해결 가능

preferredFont & UIFontMetrics

  • 사용자가 원하는 글자 크기를 선택할 수 있다.
  • 해당 타입은 위의 블로그에서 참조했을 때 Dynamic Type이다.
  • UIFont.systemFont(ofSize:weight:) 를 사용하면 Dynamic Type을 지원할 수 없다.

  • 스토리보드에서는 Test Style 중 하나를 선택해야 한다.

강의

private func centeredAttributedString(_ string: String, fontSize: CGFloat) -> NSAttributedString {
    var font = UIFont.preferredFont(forTextStyle: .body).withSize(fontSize)
    font = UIFontMetrics(forTextStyle: .body).scaledFont(for: font) // 폰트 크기조절에 따론 폰트 설정
    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.alignment = .center
    return NSAttributedString(string: string, attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle, .font: font])
}
// preferredFont(forTextStyle:)에 원하는 Text Style을 넣어준다.
// UIFontMetics는 Dynamic Type을 지원해줄 수 있다.(zeddios님의 블로그에 더 자세한 내용)

// 시뮬레이터에서 글자 크기를 변경했다고 해서 바로 적용이 되지 않는 문제가 발생했다.

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    setNeedsDisplay()
    setNeedsLayout()
}
// 해당 메소드를 사용해주면 글자 크기를 변경할 때마다 바로 적용이 된다.


setNeedsDisplay & setNeedsLayout

setNeedsDisplay

  • 과정
    1. View 업데이트 필요성 발생
    2. 직접 draw(_ rect:) 메소드를 호출하지 않는다.
    3. setNeedsDisplay()를 호출해야 한다.
    4. 다음 drawing cycle에 업데이트
    5. View 업데이트

setNeedsLayout

  • layoutSubViews() : SubView들을 배치한다.
  • 위의 메소드도 직접 호출하면 안 된다. 그래서 업데이트를 해주려면 setNeedsLayout()를 호출해줘야 한다. 위의 과정과 비슷하다.

constraints priority

  • 전체적으로 Safe Area 모서리 부분에 16의 범위를 줘서 넘어가지 않도록 했다.
  • 강의에서 원하는건 해당 핸드폰에서 5:8의 비율로 가장 큰 카드 모양을 만들고 싶었다.
  • 특이했던건 Width를 800으로 놔뒀는데 이 자체가 범위를 넘어가는 범위라서 오류가 발생한다.
  • 하지만 Priority를 1000(Required)에서 750(High)로 수정해줌으로써 오류가 발생하지 않았다.
  • 중요하지 않은 제한 조건의 우선 순위를 낮게 놔둔다.

UISwipeGestureRecognizer

@IBOutlet weak var playingCardView: PlayingCardView! {
    didSet {
        let swipe = UISwipeGestureRecognizer(target: self, action: #selector(nextCard))
        swipe.direction = [.left, .right]
        playingCardView.addGestureRecognizer(swipe)
    }
}
    
@objc func nextCard() {
    if let card = deck.draw() {
        playingCardView.rank = card.rank.order
        playingCardView.suit = card.suit.rawValue
    }
}
// 스토리보드 @IBOutlet을 사용하는 방법이다.
// 주로 didSet을 활용해서 view에게 동작하도록 만든다.
// swipe 방향을 왼쪽, 오른쪽으로 설정

  • 좌, 우로 스와이프 시 새로운 카드 등장

UITapGestureRecognizer

  • Tap Gesutre Recognizer 를 추가해주고 @IBAction 메소드 추가
@IBAction func flipCard(_ sender: UITapGestureRecognizer) {
    switch sender.state {
    case .ended:
        playingCardView.isFaceUp = !playingCardView.isFaceUp
    default:
        break
    }
}


UIPinchGestureRecognizer

// PlayingCardView.swift
var faceCardScale: CGFloat = SizeRatio.faceCardImageSizeToBoundsSize {
    didSet {
        setNeedsDisplay()
    }
}
    
@objc func adjustFaceCardScale(byHandlingGestureRecognizedBy recognizer: UIPinchGestureRecognizer) {
    switch recognizer.state {
    case .changed, .ended:
        faceCardScale *= recognizer.scale
        recognizer.scale = 1.0
    default:
        break
    }
}

// ViewController.swift
@IBOutlet weak var playingCardView: PlayingCardView! {
    didSet {
        let pinch = UIPinchGestureRecognizer(target: playingCardView, action: #selector(playingCardView.adjustFaceCardScale(byHandlingGestureRecognizedBy:)))
        playingCardView.addGestureRecognizer(pinch)
    }
}


링크

좋은 웹페이지 즐겨찾기