ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Playground를 이용한 UI 프로토타입 설계
    iOS Swift 2020. 9. 9. 18:51

    Swift 에서 스토리보드를 이용해 앱을 구현할 경우 SwiftUI의 장점 중 하나인 프리뷰를 이용할 수 없다. 프리뷰를 이용해 UI를 설계할 경우 스토리보드 보다 빠른 속도로 구현할 수 있고 구현을 바로 프리뷰를 통해서 확인할 수 있다. 이런 장점을 스토리보드를 사용하지 않고  Playground에서 Live view를 통해서 비슷하게 개발 시 이용할 수 있다. 특히 스토리보드 없이 코드로 뷰를 설계할 때에는 해당 구현을 바로 실제 설계에 반영할 수 도 있다. 

     

    프로토타입 구현을 위해 우선 새로운 플레이그라운들 생성하는 데 Single View를 선택한다. 

     

    생성된 파일은 UIViewController를 상속받은 MyViewController 클래스가 뷰에 Hello world!를 표시한다.

    우선 loadView() 내부의 코드 중 UILable과 관련된 코드를 지운다. 카드를 표현할 UIView와 백그라운드 이미지를 위한 UIImageView, 타이틀을 위한 UILabel을 추가한다. 카드 뷰에 백그라운드의 이미지로 사용할 Cover.jpg 파일을 Resource 폴더로 이동시킨다.

     

    이제 viewDidLoad()를 재정의해 각 컴포넌트들의 AutoLayout으로 정렬하고 플레이 버튼을 눌러 현재까지 구성된 화면을 확인한다. 

     

    AutoLayout을 코드로 사용하기 위해서는 UIView 객체에 translatesAutoresizingMaskIntoConstraints을 false 값으로 설정해야 하고, 설정된 constraint에 대해서는 isActive를 true로 설정해야 한다.

        //cardView의 auto constraint를 설정하기 위해서는 translatesAutoresizingMaskIntoConstraints
        //를 false로 설정 
        cardView.translatesAutoresizingMaskIntoConstraints = false
        //cardView는 top 위치에서 y 축으로 120 이동 
        topConstraint = cardView.topAnchor.constraint(equalTo: view.topAnchor, constant: 120)
          //이 constraint가 설정되기 위해서 isActive를 true로 설정 
          topConstraint.isActive = true
        //왼쪽, 오른쪽 constraint를 부모뷰와 동일한 값으로 설정 
        leadingConstraint =  cardView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30)
          leadingConstraint.isActive = true
        trailgConstraint =  cardView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30)
          trailgConstraint.isActive = true
          //높이 constraint 설정 
        heightConstraint = cardView.heightAnchor.constraint(equalToConstant: 250)
        heightConstraint.isActive = true
        
       

    다음으로 카드뷰에 탭 제스쳐을 추가한다.  

     override func loadView() {
        ...
        let tap = UITapGestureRecognizer(target: self, action: #selector(cardViewTapped))
        cardView.addGestureRecognizer(tap)
        view.addSubview(cardView)
        self.view = view
      }
      
      @objc func cardViewTapped() {
        descriptionMode = true
        updateUI()
      }
      
      private func updateUI() {
        print("updateUI")
      }

    이제 updateUI 에 탭에 따라서 카드 뷰가 이동하도록 애니매이션을 추가한다. 

      @objc func cardViewTapped() {
        descriptionMode.toggle()
        updateUI()
      }
      
      func updateUI() {
        UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0, options: []) {
          if self.descriptionMode  {
            self.topConstraint.constant = 0
            self.leadingConstraint.constant = 0
            self.trailgConstraint.constant = 0
            self.heightConstraint.constant = 300
            self.cardView.layer.cornerRadius = 0
            self.coverImageView.layer.cornerRadius = 0
            
          } else {
            self.topConstraint.constant = 120
            self.leadingConstraint.constant = 30
            self.trailgConstraint.constant = -30
            self.heightConstraint.constant = 250
            self.cardView.layer.cornerRadius = 0
            
            self.cardView.layer.cornerRadius = 14
            self.coverImageView.layer.cornerRadius = 14
          }
          self.cardView.layoutIfNeeded()
          
        }
      }

    여기서 중요한 점은 화면의 레이아웃이 변경되도록 self.cardView.layoutIfNeeded() 를 호출해 주어야 한다는 점이다.

    카드 뷰가 상단으로 이동하면 페이지에 대한 상세 내용을 보여주도록 descriptionLabel을 추가하고 alpha 값을 이용해 뷰가 상단에 있을 시 아래 화면에 상세 내용을 보여주도록 수정한다. 추가적으로 상단에 카드뷰 가 위치 시 닫기 버튼을 추가해 닫기 버튼을 누를 시 화면이 중앙으로 이동하다록 수정한다. 

     func updateUI() {
        UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0, options: []) {
          if self.descriptionMode  {
            ...
            self.descriptionLable.alpha = 1
            self.closeButton.alpha = 1
            
          } else {
            ...
            self.descriptionLable.alpha = 0
            self.closeButton.alpha = 0
          }
          self.cardView.layoutIfNeeded()
          
        }
      }

     

    전체 구현 코드는 아래와 같다. 

    //: A UIKit based Playground for presenting user interface
    
    import UIKit
    import PlaygroundSupport
    
    class MyViewController : UIViewController {
      let cardView = UIView()
      let titleLabel = UILabel()
      let coverImageView = UIImageView()
      let descriptionLable = UILabel()
      let closeButton = UIButton()
      var topConstraint : NSLayoutConstraint!
      var leadingConstraint : NSLayoutConstraint!
      var trailgConstraint : NSLayoutConstraint!
      var heightConstraint : NSLayoutConstraint!
      
      var descriptionMode = false;
      
      override func loadView() {
        let view = UIView()
        view.backgroundColor = .white
        
        cardView.backgroundColor = .white
        cardView.layer.cornerRadius = 14
        cardView.layer.shadowOpacity = 0.25
        cardView.layer.shadowOffset = CGSize(width: 0, height: 10)
        cardView.layer.shadowRadius = 10;
        
        titleLabel.text = "플레이그라운드를 이용한 프로토타입"
        titleLabel.font = UIFont.systemFont(ofSize: 20, weight: .semibold)
        titleLabel.textColor = .white
        
        coverImageView.contentMode = .scaleAspectFill
        coverImageView.image = UIImage(named: "Cover.jpg")
        coverImageView.clipsToBounds = true
        coverImageView.layer.cornerRadius = 14
        
        closeButton.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0.5)
        closeButton.layer.cornerRadius = 14
        closeButton.setImage(#imageLiteral(resourceName: "Action-Close@2x.png"), for: .normal)
        closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside)
        closeButton.alpha = 0
        
        descriptionLable.text = "플레이그라운드의 Single View를 이용해 SwiftUI의 Preview와 비슷하게 프로토타입을 디자인할 수 있다. 이를 통해 빠르게 디자인과 애니메이션을 확인할 수 있다. 여기서는 카드를 터치 할 시 카드가 위로 이동하면서 상세내용을 표시하고 종료 버튼을 누를 시 처음 카드 화면으로 이동하는 뷰를 디자인하기 위해 카드뷰를 생성하고 top, leading, trailing height의 constraint를 설정하고 이에 따라 내부 이미지뷰와 레이블을 AutoLayout으로 프로그램을 통해서 설정한다."
        descriptionLable.textColor = .black
        descriptionLable.numberOfLines = 0
        descriptionLable.alpha = 0
        
        cardView.addSubview(coverImageView)
        cardView.addSubview(titleLabel)
        cardView.addSubview(closeButton)
        cardView.addSubview(descriptionLable)
        
        let tap = UITapGestureRecognizer(target: self, action: #selector(cardViewTapped))
        cardView.addGestureRecognizer(tap)
        
        view.addSubview(cardView)
        
        self.view = view
      }
      override func viewDidLoad() {
        super.viewDidLoad()
      
        cardView.translatesAutoresizingMaskIntoConstraints = false
        topConstraint = cardView.topAnchor.constraint(equalTo: view.topAnchor, constant: 120)
          topConstraint.isActive = true
        leadingConstraint =  cardView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30)
          leadingConstraint.isActive = true
        trailgConstraint =  cardView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30)
          trailgConstraint.isActive = true
        heightConstraint = cardView.heightAnchor.constraint(equalToConstant: 250)
        heightConstraint.isActive = true
        
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        titleLabel.topAnchor.constraint(equalTo: cardView.topAnchor, constant: 16).isActive = true
        titleLabel.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16).isActive = true
        titleLabel.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: 16).isActive = true
        titleLabel.heightAnchor.constraint(equalToConstant: 38).isActive = true
        coverImageView.translatesAutoresizingMaskIntoConstraints = false
        coverImageView.topAnchor.constraint(equalTo: cardView.topAnchor).isActive = true
        coverImageView.bottomAnchor.constraint(equalTo: cardView.bottomAnchor).isActive = true
        coverImageView.leadingAnchor.constraint(equalTo: cardView.leadingAnchor).isActive = true
        coverImageView.trailingAnchor.constraint(equalTo: cardView.trailingAnchor).isActive = true
        
        closeButton.translatesAutoresizingMaskIntoConstraints = false
        closeButton.topAnchor.constraint(equalTo: cardView.topAnchor, constant: 20).isActive = true
        closeButton.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -20).isActive = true
        closeButton.widthAnchor.constraint(equalToConstant: 28).isActive = true
        closeButton.heightAnchor.constraint(equalToConstant: 28).isActive = true
        
        descriptionLable.translatesAutoresizingMaskIntoConstraints = false
        descriptionLable.topAnchor.constraint(equalTo: cardView.bottomAnchor, constant: 90).isActive = true
        descriptionLable.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16).isActive = true
        descriptionLable.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -16).isActive = true
        descriptionLable.heightAnchor.constraint(equalToConstant: 300)
     
      }
      
      @objc func cardViewTapped() {
        descriptionMode = true
        updateUI()
      }
      
      @objc func closeButtonTapped() {
        descriptionMode = false
        updateUI()
      }
      
      func updateUI() {
        UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0, options: []) {
          if self.descriptionMode  {
            self.topConstraint.constant = 0
            self.leadingConstraint.constant = 0
            self.trailgConstraint.constant = 0
            self.heightConstraint.constant = 300
            self.cardView.layer.cornerRadius = 0
            self.coverImageView.layer.cornerRadius = 0
            self.descriptionLable.alpha = 1
            self.closeButton.alpha = 1
            
          } else {
            self.topConstraint.constant = 120
            self.leadingConstraint.constant = 30
            self.trailgConstraint.constant = -30
            self.heightConstraint.constant = 250
            self.cardView.layer.cornerRadius = 0
            self.cardView.layer.cornerRadius = 14
            self.coverImageView.layer.cornerRadius = 14
            self.descriptionLable.alpha = 0
            self.closeButton.alpha = 0
          }
          self.cardView.layoutIfNeeded()
          
        }
      }
    }
    // Present the view controller in the Live View window
    PlaygroundPage.current.liveView = MyViewController()
    

     

    ProtoType.playground.zip
    0.16MB

    댓글

Designed by Tistory.