iOS FSCalendar 써본것 모두다 싹다! 정리

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


FSCalendar

이번에 달력을 구현함에 있어서 FSCalendar라는 라이브러리를 사용한 경험이 있었습니다.
모든 기능을 다 써본것은 아니지만, 그래도 써본 모든 기능에 대해서 정리를 해보려 합니다!

기본 셋팅

여기서 중요한거… delegate들 꼭.. 꼭.. 가져오세요..
첨에 아니 이거 사용하려면 스택오버플로우에 이런 함수있다는데.. 대체 왜!! 왜 나는 안불러져 오는거지..?! 했는데

응~ FSCalendarDelegateAppearance 이거 채택 안했구요..ㅎ

꼭.. 꼭.. 잊지마세요!
저 같은 바보같은 행동 하지마세요.흑ㅎ극…

class CalendarViewController: UIViewController, FSCalendarDelegate, FSCalendarDataSource, FSCalendarDelegateAppearance {
    @IBOutlet weak var calendar: FSCalendar!
    var selectedDate: Date = Date()

    override func viewDidLoad() {
        super.viewDidLoad()
        setCalendarUI()
    }

    // 오늘 날짜로 돌아오는 버튼 및 액션 
    @IBOutlet weak var currentBtn: UIButton!
    @IBAction func currentBtnClicked(_ sender: Any) {
        self.calendar.select(Date.Today())
    }

    // 현재 달로 돌아오기 위해서는 
    @IBAction func currentMonthBtnClicked(_ sender: Any) {
        self.calendar.setCurrentPage(Date(), animated: true)
    }

    func setCalendarUI() {
        // delegate, dataSource 
        self.calendar.delegate = self
        self.calendar.dataSource = self
        
        // calendar locale > 한국으로 설정 
        self.calendar.locale = Locale(identifier: "ko_KR")
        
        // 상단 요일을 한글로 변경 
        self.calendar.calendarWeekdayView.weekdayLabels[0].text = "일"
        self.calendar.calendarWeekdayView.weekdayLabels[1].text = "월"
        self.calendar.calendarWeekdayView.weekdayLabels[2].text = "화"
        self.calendar.calendarWeekdayView.weekdayLabels[3].text = "수"
        self.calendar.calendarWeekdayView.weekdayLabels[4].text = "목"
        self.calendar.calendarWeekdayView.weekdayLabels[5].text = "금"
        self.calendar.calendarWeekdayView.weekdayLabels[6].text = "토"
        
        // 월~일 글자 폰트 및 사이즈 지정
        self.calendar.appearance.weekdayFont = UIFont.SpoqaHanSans(type: .Regular, size: 14)
        // 숫자들 글자 폰트 및 사이즈 지정
        self.calendar.appearance.titleFont = UIFont.SpoqaHanSans(type: .Regular, size: 16)
        
        // 캘린더 스크롤 가능하게 지정
        self.calendar.scrollEnabled = true
        // 캘린더 스크롤 방향 지정 
        self.calendar.scrollDirection = .horizontal
        
        // Header dateFormat, 년도, 월 폰트(사이즈)와 색, 가운데 정렬
        self.calendar.appearance.headerDateFormat = "YYYY년 MM월"
        self.calendar.appearance.headerTitleFont = UIFont.SpoqaHanSans(type: .Bold, size: 20)
        self.calendar.appearance.headerTitleColor = UIColor(named: "FFFFFF")?.withAlphaComponent(0.9)
        self.calendar.appearance.headerTitleAlignment = .center
        
        // 요일 글자 색
        self.calendar.appearance.weekdayTextColor = UIColor(named: "F5F5F5")?.withAlphaComponent(0.2)
        
        // 캘린더 높이 지정 
        self.calendar.headerHeight = 68        
        // 캘린더의 cornerRadius 지정 
        self.calendar.layer.cornerRadius = 10
        
        // 양옆 년도, 월 지우기
        self.calendar.appearance.headerMinimumDissolvedAlpha = 0.0
    
        // 달에 유효하지 않은 날짜의 색 지정 
        self.calendar.appearance.titlePlaceholderColor = UIColor.white.withAlphaComponent(0.2)
        // 평일 날짜 색
        self.calendar.appearance.titleDefaultColor = UIColor.white.withAlphaComponent(0.5)
        // 달에 유효하지않은 날짜 지우기 
        self.calendar.placeholderType = .none
        
        // 캘린더 숫자와 subtitle간의 간격 조정 
        self.calendar.appearance.subtitleOffset = CGPoint(x: 0, y: 4)
        
        self.calendar.select(selectedDate)
    }

    // 날짜를 선택했을 때 할일을 지정 
    func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
        self.navigationController?.popViewController(animated: true)
        self.delegate?.dateUpdated(date: date.key)
    }

    // 선택된 날짜의 채워진 색상 지정 
    func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, fillSelectionColorFor date: Date) -> UIColor? {
        return appearance.selectionColor
    }

    // 선택된 날짜 테두리 색상
    func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, borderSelectionColorFor date: Date) -> UIColor? {
        return UIColor.white.withAlphaComponent(1.0)
    }
    
    // 모든 날짜의 채워진 색상 지정 
    func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, fillDefaultColorFor date: Date) -> UIColor? {
        return UIColor.white
    }
    
    // title의 디폴트 색상 
    func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, titleDefaultColorFor date: Date) -> UIColor? {
        return UIColor.white.withAlphaComponent(0.5)
    }
    
    // subtitle의 디폴트 색상 
    func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, subtitleDefaultColorFor date: Date) -> UIColor? {
        return UIColor.white.withAlphaComponent(0.5)
    }
    
    // 원하는 날짜 아래에 subtitle 지정 
    // 오늘 날짜에 오늘이라는 글자를 추가해보았다
    func calendar(_ calendar: FSCalendar, subtitleFor date: Date) -> String? {
        switch dateFormatter.string(from: date) {
        case dateFormatter.string(from: Date()):
            return "오늘"
        default:
            return nil
        }
    }

    // 날짜의 글씨 자체를 오늘로 바꾸기 
    func calendar(_ calendar: FSCalendar, titleFor date: Date) -> String? {
        switch dateFormatter.string(from: date) {
        case dateFormatter.string(from: Date()):
            return "오늘"
        default:
            return nil
        }
    }
}

이 외에도 커스텀할 수 있는 다양한 요소들이 있습니다.
함수를 호출해서 사용하는 방법은 여러가지이니 각자 취향에 맞게 사용하면 좋을 것 같습니다 :)

Xcode Error, Unable to boot device because it cannot be located on disk

|

개인적인 연습 내용을 정리한 글입니다.
더 좋은 방법이 있거나, 잘못된 부분이 있으면 편하게 의견 주세요. :)


Unable to boot device because it cannot be located on disk

이것 저것 삽질을 하던 중, 진짜 삽질을 했다.
파인더에서 뭐가 엄청 지저분해보이길래.. 그냥 삭제를 했다.

파일을 찾는데, 이거 삭제하고 다시 빌드를 하면 생기겠지.. 하는 안일한 생각과 함께 ..

그랬더니 Unable to boot device because it cannot be located on disk 이런 에러가 두두등장

검색해보니, 프로젝트를 실행해서 시뮬레이터를 재생하려는데 해당 시뮬레이터가 지정된 위치에 없을때 발생하는것이란다.
고로 내가 시뮬레이터들을 모조리 삭제했다는 것을 의미하는 것 같은데.. 맞는 것 같았다 .ㅎ…

해결방법은 간단하다.

  1. 현재 열려있는 Xcode와 시뮬레이터를 모두 종료시킨다.
  2. 터미널을 키고 아래 명령어를 적어준다.
xcrun simctl erase all

그러면 에러 해결 완료!

iOS SCPageControl 사용해보기

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


SCPageControl 사용해보기

이번엔 페이지 컨트롤의 라이브러리인 SCPageControl를 사용하는 방법에 대해 정리해보겠습니당

방법은 엄청 간단합니다.

class HomeTableViewCell: UITableViewCell {
    @IBOutlet weak var collectionView: UICollectionView!
    @IBOutlet weak var sc: SCPageControlView!

    override func awakeFromNib() {
        super.awakeFromNib()
        // 페이지컨트롤에 필요한 페이지 수/페이지가 시작할 위치/현재 보여지는 페이지일때 페이지컨트롤의 색/기본 색 지정 
        sc.set_view(2, current: 0, current_color: UIColor(named: "7A7BDA")!, disable_color: nil)
        // 페이지 컨트롤 스타일 지정 
        sc.scp_style = .SCJAFlatBar
    }
}

extension HomeTableViewCell: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        sc.scroll_did(scrollView)
    }
}

이때 여기서 가장 중요한 포인트는 코드에 있지않습니다.

일반적으로 생각해보면 페이지컨트롤이 컬렉션뷰 안에 존재할것같다고 생각할수도 있습니다.
그렇지만 그렇지 않습니다. 컬렉션뷰 바깥에 같은 동일선상에 존재하고 있어야 합니다.

이렇게 말이죠!

iOS MKMagneticProgress를 사용해 circleProgressBar 만들어보기

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


반원 짜리 모양을 가진 프로그래스 뷰를 만들고 싶었습니다.
앞서 헀던 방식으로 직접 뷰를 그릴수도 있지만, 이번에는 라이브러리를 통해서 만들어보려고 합니다.

MKMagneticProgress를 사용해 circleProgressBar 만들어보기

MKMagneticProgress

import MKMagneticProgress

class HomeTableViewCell: UITableViewCell {
    @IBOutlet weak var circleProgressBar: MKMagneticProgress!

    override func awakeFromNib() {
        super.awakeFromNib()

        setProgressViewUI()
    }
    
    func setProgressViewUI() {
        circleProgressBar.setProgress(progress: 0)

        circleProgressBar.progressShapeColor = UIColor(named: "7A7BDA")!
        circleProgressBar.backgroundShapeColor = (UIColor(named: "FFFFFF")?.withAlphaComponent(0.1))!
        circleProgressBar.titleColor = UIColor.red
        circleProgressBar.percentColor = UIColor.white
        
        
        circleProgressBar.lineWidth = 16
        circleProgressBar.orientation = .bottom
        circleProgressBar.lineCap = .round
    }
}

무척 간단합니다.
여기서 중요한 점은 setProgress에서 progress로 받는 숫자는 0~1사이의 숫자입니다.

전체를 1로 보고 그 사이의 숫자들을 표현해주고 있는 것을 의미합니다.
따라서 해당하는 progress에 유의미한 그래프를 보여주고 싶다면 반드시 0과 1사이의 숫자로 표현해서 보여줘야함을 잊지말아야 합니다!

iOS UIBezierPath를 사용해 custom circleProgressBar 만들어보기

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


UIBezierPath를 사용해 custom circleProgressBar 만들어보기

우선 view 파일을 하나를 만들어줍니다.

import UIKit

class CircleProgressBarView: UIView {
    // 전체 원을 그려줄 layer
    private var circleLayer = CAShapeLayer()
    // 프로그래스 원을 그려줄 layer
    private var progressLayer = CAShapeLayer()
    
    // 원의 시작과 끝을 그려줄 변수 
    private var startPoint = CGFloat(-Double.pi / 2)
    private var endPoint = CGFloat(3 * Double.pi / 2)
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        createCirclePath()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }

    func createCirclePath() {
        let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: 120, startAngle: startPoint, endAngle: endPoint, clockwise: true)

        // 전체 원 setUI
        circleLayer.path = circlePath.cgPath
        circleLayer.fillColor = UIColor.clear.cgColor
        circleLayer.lineCap = .round
        circleLayer.lineWidth = 10.0
        circleLayer.strokeStart = 0.0
        circleLayer.strokeEnd = 1.0
        circleLayer.strokeColor = UIColor.lightGray.withAlphaComponent(0.2).cgColor
        
        layer.addSublayer(circleLayer)
        
        // 프로그래스 원 UI
        progressLayer.path = circlePath.cgPath
        progressLayer.fillColor = UIColor.clear.cgColor
        progressLayer.lineCap = .round
        progressLayer.lineWidth = 10.0
        progressLayer.strokeStart = 0
        progressLayer.strokeEnd = 0
        progressLayer.strokeColor = UIColor(named: "7A7BDA")?.cgColor
        
        layer.addSublayer(progressLayer)
    
    }

    // 프로그래스 원에 애니메이션을 주고싶다면 사용 
    func progressAnimation(duration: TimeInterval, value: Float, maxValue: Float) {
        let circleProgressAnimation = CABasicAnimation(keyPath: "strokeEnd")
    
        circleProgressAnimation.duration = duration
        circleProgressAnimation.fromValue = 0
        
        progressLayer.strokeStart = 0
        progressLayer.strokeEnd = CGFloat(value/maxValue)
        progressLayer.add(circleProgressAnimation, forKey: "progressAnim")
    }
}

일반적으로 progressAnimation함수에는 maxValue가 들어가있지 않은 예제만 있을 것이다.
근데 저는 전체 원의 최대값을 만들어 그 최대값 대비 value를 넣어줄 예정이기에 인자를 하나 더 받았습니다.ㅎㅎ

이렇게 뷰를 만들었다면 이제 이 해당 뷰를 사용하는 곳에서 사용을 해줘야겠죠?

저는 테이블뷰 셀에 해당 뷰를 만들어주고 있는데, 그러다 보니 뷰의 .center가 잘 맞지 않는 이슈가 있었습니다.
이를 해결하기 위해 snapKit을 사용해 뷰의 위치를 다시 잡아주었습니다.

HomeTableViewCell

import UIKit
import SnapKit

class ProgressTableViewCell: UITableViewCell {
    @IBOutlet weak var containerView: UIView!
    
    var circleProgressView: CircleProgressBarView!
    var circleDuration: TimeInterval = 2

    var stepData = Int()

    override func awakeFromNib() {
        super.awakeFromNib()

        setupCircleProgressBarView()
    }

    func setupCircleProgressBarView() {
        circleProgressView = CircleProgressBarView()
        self.containerView.addSubview(circleProgressView)

        circleProgressView.snp.makeConstraints { make in
            make.centerX.centerY.equalTo(self.containerView)
            make.top.equalTo(self.containerView).inset(165)
        }
        
        self.circleDuration = 2
        self.circleProgressView.progressAnimation(duration: circleDuration, value: Float(self.stepData), maxValue: 10000)
        
        self.containerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap)))
        self.circleProgressView.layoutIfNeeded()
    }
    
    @objc func handleTap() {
        self.circleDuration = 2
        self.circleProgressView.progressAnimation(duration: circleDuration, value: Float(self.stepData), maxValue: 10000)
    }
}