iOS 프로그래밍 11주차

CDH·2024년 11월 13일

출처 : Do it! 스위프트로 아이폰 앱 만들기 입문, 송호정, 이범근 저,이지스퍼블리싱, 2023년 01월 20일
https://www.yes24.com/Product/Goods/116918114

https://github.com/doitswift/example
02 Hello World 앱 만들며 Xcode에 완벽 적응하기
03 원하는 이미지 화면에 출력하기 - 이미지 뷰
04 데이트 피커 사용해 날짜 선택하기
05 피커 뷰 사용해 원하는 항목 선택하기
06 얼럿 사용해 경고 표시하기
07 웹 뷰로 간단한 웹 브라우저 만들기
08 맵 뷰로 지도 나타내기
09 페이지 이동하기 - 페이지 컨트롤
10 탭 바 컨트롤러 이용해 여러 개의 뷰 넣기
11 내비게이션 컨트롤러 이용해 화면 전환하기
12 테이블 뷰 컨트롤러 이용해 할 일 목록 만들기
13 음악 재생하고 녹음하기
14 비디오 재생 앱 만들기
15 카메라와 포토 라이브러리에서 미디어 가져오기
16 코어 그래픽스로 화면에 그림 그리기
17 탭과 터치 사용해 스케치 앱 만들기
18 스와이프 제스처 사용하기
19 핀치 제스처 사용해 사진을 확대/축소하기


소스를 처음 볼 때는 Main.storyboard 부터 보기

09 페이지 이동하기 - 페이지 컨트롤

생성형 AI로 주석 설명

//
//  ViewController.swift
//  PageControl
//
//  Created by Ho-Jeong Song on 2021/11/25.
//

import UIKit // UIKit 프레임워크를 임포트하여 UI 요소를 사용할 수 있게 함

// 이미지 파일 이름을 담고 있는 문자열 배열
var images = [ "01.png", "02.png", "03.png", "04.png", "05.png", "06.png" ]

// ViewController 클래스 정의, UIViewController를 상속받음
class ViewController: UIViewController {
    // IBOutlet으로 연결된 UIImageView와 UIPageControl
    @IBOutlet var imgView: UIImageView! // 이미지 뷰를 나타내는 변수
    @IBOutlet var pageControl: UIPageControl! // 페이지 컨트롤을 나타내는 변수
    
    // 뷰가 로드된 후 호출되는 메서드
    override func viewDidLoad() {
        super.viewDidLoad() // 부모 클래스의 viewDidLoad 메서드를 호출
        
        // 페이지 컨트롤의 페이지 수를 이미지 배열의 개수로 설정
        pageControl.numberOfPages = images.count
        // 현재 페이지를 3으로 설정 (0부터 시작하므로 4번째 이미지)
        pageControl.currentPage = 3
        
        // 페이지 인디케이터의 기본 색상을 시스템 블루로 설정
        pageControl.pageIndicatorTintColor = UIColor.systemBlue
        // 현재 페이지 인디케이터의 색상을 갈색으로 설정
        pageControl.currentPageIndicatorTintColor = UIColor.brown
        
        // 이미지 뷰에 첫 번째 이미지를 설정
        imgView.image = UIImage(named: images[0])
    }

    // 페이지 컨트롤의 값이 변경될 때 호출되는 액션 메서드
    @IBAction func pageChange(_ sender: UIPageControl) {
        // 현재 페이지에 해당하는 이미지를 이미지 뷰에 설정
        imgView.image = UIImage(named: images[pageControl.currentPage])
    }
}

10 탭 바 컨트롤러 이용해 여러 개의 뷰 넣기

탭 바 컨트롤러 : 화면 밑에 탭으로 여러 개의 화면을 빠르게 진행할 수 있음


11 내비게이션 컨트롤러 이용해 화면 전환하기 (가장 많이 쓰는 방법 중 하나)

내비게이션 컨트롤러 : 버튼을 누르면 상세 정보가 나오고, 되돌아가는 버튼이 있음


12 테이블 뷰 컨트롤러 이용해 할 일 목록 만들기

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // tableView에서 지정된 셀을 재사용 가능하게 가져옴
    let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
    
    // 셀의 텍스트 레이블에 items 배열에서 indexPath에 해당하는 텍스트를 설정
    cell.textLabel?.text = items[(indexPath as NSIndexPath).row]
    
    // 셀의 이미지 뷰에 itemsImageFile 배열에서 indexPath에 해당하는 이미지 설정
    cell.imageView?.image = UIImage(named: itemsImageFile[(indexPath as NSIndexPath).row])

    // 셀을 반환하여 테이블 뷰에 표시
    return cell
}

GPT 활용 (질문 : iOS 앱을 만들 때 가장 많이 사용하는 컨트롤러, UI)

자주 쓰이는 컨트롤러 요소:
UIViewController - 기본 뷰 컨트롤러로, 거의 모든 화면에 사용됨.
UITableViewController - 목록형 데이터를 표시하는 테이블 뷰 컨트롤러.
UICollectionViewController - 그리드 형태의 데이터를 표시하는 컬렉션 뷰 컨트롤러.
UINavigationController - 뷰 간의 계층적 탐색을 위한 네비게이션 바.
UITabBarController - 하단 탭 바를 사용하여 화면 간 전환을 가능하게 함.

자주 쓰이는 UI 요소:
UILabel - 텍스트를 표시하는 레이블.
UIButton - 버튼.
UIImageView - 이미지를 표시하는 이미지 뷰.
UITextField - 사용자 입력을 받는 텍스트 필드.
UIScrollView - 화면을 스크롤할 수 있게 하는 스크롤 뷰.


13 음악 재생하고 녹음하기

import UIKit
import AVFoundation

// ViewController 클래스 정의 - 오디오 재생 및 녹음 기능 포함
class ViewController: UIViewController, AVAudioPlayerDelegate, AVAudioRecorderDelegate {
    
    // 오디오 플레이어와 파일 URL 선언
    var audioPlayer : AVAudioPlayer!
    var audioFile : URL!
    
    // 최대 볼륨 상수
    let MAX_VOLUME : Float = 10.0
    
    // 타이머 선언
    var progressTimer : Timer!
    
    // 시간 업데이트를 위한 셀렉터 선언
    let timePlayerSelector:Selector = #selector(ViewController.updatePlayTime)
    let timeRecordSelector:Selector = #selector(ViewController.updateRecordTime)

    // UI 아웃렛 연결
    @IBOutlet var pvProgressPlay: UIProgressView!
    @IBOutlet var lblCurrentTime: UILabel!
    @IBOutlet var lblEndTime: UILabel!
    @IBOutlet var btnPlay: UIButton!
    @IBOutlet var btnPause: UIButton!
    @IBOutlet var btnStop: UIButton!
    @IBOutlet var slVolume: UISlider!
    
    @IBOutlet var btnRecord: UIButton!
    @IBOutlet var lblRecordTime: UILabel!
    
    // 오디오 레코더와 녹음 모드 상태 변수 선언
    var audioRecorder : AVAudioRecorder!
    var isRecordMode = false
    
    // 뷰가 로드되었을 때 실행되는 함수
    override func viewDidLoad() {
        super.viewDidLoad()
        // 오디오 파일 선택
        selectAudioFile()
        
        // 녹음 모드인지 아닌지에 따라 초기 설정
        if !isRecordMode {
            initPlay()
            btnRecord.isEnabled = false // 녹음 버튼 비활성화
            lblRecordTime.isEnabled = false // 녹음 시간 레이블 비활성화
        } else {
            initRecord()
        }
    }
    
    // 오디오 파일을 선택하는 함수
    func selectAudioFile() {
        if !isRecordMode {
            // 재생 모드일 때 번들에서 오디오 파일 가져옴
            audioFile = Bundle.main.url(forResource: "Sicilian_Breeze", withExtension: "mp3")
        } else {
            // 녹음 모드일 때 도큐먼트 디렉토리에 파일 저장 위치 설정
            let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            audioFile = documentDirectory.appendingPathComponent("recordFile.m4a")
        }
    }
    
    // 녹음 초기화 함수
    func initRecord() {
        // 녹음 설정 구성
        let recordSettings = [
            AVFormatIDKey : NSNumber(value: kAudioFormatAppleLossless as UInt32),
            AVEncoderAudioQualityKey : AVAudioQuality.max.rawValue,
            AVEncoderBitRateKey : 320000,
            AVNumberOfChannelsKey : 2,
            AVSampleRateKey : 44100.0
        ] as [String : Any]
        
        do {
            // 오디오 레코더 인스턴스 생성
            audioRecorder = try AVAudioRecorder(url: audioFile, settings: recordSettings)
        } catch let error as NSError {
            print("Error-initRecord : \(error)")
        }
        
        audioRecorder.delegate = self
        slVolume.value = 1.0 // 볼륨 초기화
        audioPlayer.volume = slVolume.value // 오디오 플레이어 볼륨 설정
        lblEndTime.text = convertNSTimeInterval2String(0)
        lblCurrentTime.text = convertNSTimeInterval2String(0)
        setPlayButtons(false, pause: false, stop: false)
        
        let session = AVAudioSession.sharedInstance()
        do {
            // 오디오 세션 설정
            try session.setCategory(.playAndRecord, mode: .default)
            try session.setActive(true)
        } catch let error as NSError {
            print("Error-setCategory : \(error)")
        }
    }
    
    // 재생 초기화 함수
    func initPlay() {
        do {
            // 오디오 플레이어 인스턴스 생성
            audioPlayer = try AVAudioPlayer(contentsOf: audioFile)
        } catch let error as NSError {
            print("Error-initPlay : \(error)")
        }
        
        slVolume.maximumValue = MAX_VOLUME // 최대 볼륨 설정
        slVolume.value = 1.0 // 초기 볼륨 설정
        pvProgressPlay.progress = 0 // 프로그레스 초기화
        
        audioPlayer.delegate = self
        audioPlayer.prepareToPlay() // 재생 준비
        audioPlayer.volume = slVolume.value
        
        lblEndTime.text = convertNSTimeInterval2String(audioPlayer.duration) // 전체 시간 설정
        lblCurrentTime.text = convertNSTimeInterval2String(0) // 현재 시간 초기화
        setPlayButtons(true, pause: false, stop: false)
    }
    
    // 재생/일시정지/정지 버튼의 상태를 설정하는 함수
    func setPlayButtons(_ play: Bool, pause: Bool, stop: Bool) {
        btnPlay.isEnabled = play
        btnPause.isEnabled = pause
        btnStop.isEnabled = stop
    }
    
    // 시간을 문자열로 변환하는 함수
    func convertNSTimeInterval2String(_ time: TimeInterval) -> String {
        let min = Int(time/60)
        let sec = Int(time.truncatingRemainder(dividingBy: 60))
        let strTime = String(format: "%02d:%02d", min, sec)
        return strTime
    }

    // 재생 버튼을 눌렀을 때 호출되는 함수
    @IBAction func btnPlayAudio(_ sender: UIButton) {
        audioPlayer.play()
        setPlayButtons(false, pause: true, stop: true) // 버튼 상태 설정
        progressTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: timePlayerSelector, userInfo: nil, repeats: true)
    }
    
    // 재생 시간을 업데이트하는 함수
    @objc func updatePlayTime() {
        lblCurrentTime.text = convertNSTimeInterval2String(audioPlayer.currentTime)
        pvProgressPlay.progress = Float(audioPlayer.currentTime/audioPlayer.duration)
    }
    
    // 일시정지 버튼을 눌렀을 때 호출되는 함수
    @IBAction func btnPauseAudio(_ sender: UIButton) {
        audioPlayer.pause()
        setPlayButtons(true, pause: false, stop: true)
    }
    
    // 정지 버튼을 눌렀을 때 호출되는 함수
    @IBAction func btnStopAudio(_ sender: UIButton) {
        audioPlayer.stop()
        audioPlayer.currentTime = 0 // 재생 위치 초기화
        lblCurrentTime.text = convertNSTimeInterval2String(0)
        setPlayButtons(true, pause: false, stop: false)
        progressTimer.invalidate() // 타이머 중지
    }
    
    // 볼륨 슬라이더 값 변경 시 호출되는 함수
    @IBAction func slChangeVolume(_ sender: UISlider) {
        audioPlayer.volume = slVolume.value
    }
    
    // 재생이 완료되었을 때 호출되는 델리게이트 함수
    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        progressTimer.invalidate()
        setPlayButtons(true, pause: false, stop: false)
    }
    
    // 녹음 모드 스위치 변경 시 호출되는 함수
    @IBAction func swRecordMode(_ sender: UISwitch) {
        if sender.isOn {
            // 녹음 모드 활성화
            audioPlayer.stop()
            audioPlayer.currentTime = 0
            lblRecordTime!.text = convertNSTimeInterval2String(0)
            isRecordMode = true
            btnRecord.isEnabled = true
            lblRecordTime.isEnabled = true
        } else {
            // 재생 모드로 전환
            isRecordMode = false
            btnRecord.isEnabled = false
            lblRecordTime.isEnabled = false
            lblRecordTime.text = convertNSTimeInterval2String(0)
        }
        selectAudioFile()
        if !isRecordMode {
            initPlay()
        } else {
            initRecord()
        }
    }
    
    // 녹음 버튼을 눌렀을 때 호출되는 함수
    @IBAction func btnRecord(_ sender: UIButton) {
        if (sender as AnyObject).titleLabel?.text == "Record" {
            audioRecorder.record()
            (sender as AnyObject).setTitle("Stop", for: UIControl.State())
            progressTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: timeRecordSelector, userInfo: nil, repeats: true)
        } else {
            audioRecorder.stop()
            progressTimer.invalidate()
            (sender as AnyObject).setTitle("Record", for: UIControl.State())
            btnPlay.isEnabled = true
            initPlay()
        }
    }
    
    // 녹음 시간을 업데이트하는 함수
    @objc func updateRecordTime() {
        lblRecordTime.text = convertNSTimeInterva

14 비디오 재생 앱 만들기

import UIKit // UIKit 프레임워크를 임포트하여 UI 요소를 사용할 수 있게 함
import AVKit // AVKit 프레임워크를 임포트하여 비디오 재생 기능을 사용할 수 있게 함

// ViewController 클래스 정의, UIViewController를 상속받음
class ViewController: UIViewController {

    // 뷰가 로드된 후 호출되는 메서드
    override func viewDidLoad() {
        super.viewDidLoad() // 부모 클래스의 viewDidLoad 메서드를 호출
        // 뷰가 로드된 후 추가적인 설정을 할 수 있는 곳
    }

    // 내부 비디오 파일 재생을 위한 버튼 액션 메서드
    @IBAction func btnPlayInternalMovie(_ sender: UIButton) {
        // 내부 파일 mp4의 경로를 가져옴
        let filePath:String? = Bundle.main.path(forResource: "FastTyping", ofType: "mp4")
        // 파일 경로를 NSURL 객체로 변환
        let url = NSURL(fileURLWithPath: filePath!)

        // 비디오 재생 메서드 호출
        playVideo(url: url)
    }
    
    // 외부 비디오 파일 재생을 위한 버튼 액션 메서드
    @IBAction func btnPlayerExternalMovie(_ sender: UIButton) {
        // 외부 URL에서 mp4 비디오 파일의 URL을 생성
        let url = NSURL(string: "https://dl.dropboxusercontent.com/s/e38auz050w2mvud/Fireworks.mp4")!

        // 비디오 재생 메서드 호출
        playVideo(url: url)
    }
    
    // 비디오 재생을 위한 private 메서드
    private func playVideo(url: NSURL)  {
        // AVPlayerViewController 인스턴스 생성
        let playerController = AVPlayerViewController()
        
        // URL을 사용하여 AVPlayer 인스턴스 생성
        let player = AVPlayer(url: url as URL)
        // AVPlayerViewController에 player 설정
        playerController.player = player
        
        // AVPlayerViewController를 현재 뷰 컨트롤러에 모달로 표시
        self.present(playerController, animated: true) {
            // 비디오 재생 시작
            player.play()
        }
    }
}

15 카메라와 포토 라이브러리에서 미디어 가져오기


16 코어 그래픽스로 화면에 그림 그리기


17 탭과 터치 사용해 스케치 앱 만들기

import UIKit // UIKit 프레임워크를 임포트하여 UI 요소를 사용할 수 있게 함

// ViewController 클래스 정의, UIViewController를 상속받음
class ViewController: UIViewController {
    @IBOutlet var imgView: UIImageView! // 이미지 뷰를 IBOutlet으로 연결

    var lastPoint: CGPoint! // 마지막 터치 위치를 저장할 변수
    var lineSize: CGFloat = 5.0 // 선의 두께를 설정
    var lineColor = UIColor.black.cgColor // 선의 색상을 검정색으로 설정

    // 뷰가 로드된 후 호출되는 메서드
    override func viewDidLoad() {
        super.viewDidLoad() // 부모 클래스의 viewDidLoad 메서드를 호출
        // 뷰가 로드된 후 추가적인 설정을 할 수 있는 곳
    }

    // 이미지 뷰를 지우기 위한 버튼 액션 메서드
    @IBAction func btnClearImageView(_ sender: UIButton) {
        imgView.image = nil // 이미지 뷰의 이미지를 nil로 설정하여 지움
    }
    
    // 터치가 시작될 때 호출되는 메서드
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first! as UITouch // 첫 번째 터치 객체를 가져옴
        
        lastPoint = touch.location(in: imgView) // 현재 터치 위치를 lastPoint에 저장
    }
    
    // 터치가 이동할 때 호출되는 메서드
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        UIGraphicsBeginImageContext(imgView.frame.size) // 이미지 컨텍스트 시작
        UIGraphicsGetCurrentContext()?.setStrokeColor(lineColor) // 선 색상 설정
        UIGraphicsGetCurrentContext()?.setLineCap(CGLineCap.round) // 선 끝 모양을 둥글게 설정
        UIGraphicsGetCurrentContext()?.setLineWidth(lineSize) // 선 두께 설정
        
        let touch = touches.first! as UITouch // 첫 번째 터치 객체를 가져옴
        let currPoint = touch.location(in: imgView) // 현재 터치 위치를 currPoint에 저장
        
        // 현재 이미지 뷰의 이미지를 컨텍스트에 그리기
        imgView.image?.draw(in: CGRect(x: 0, y: 0, width: imgView.frame.size.width, height: imgView.frame.size.height))
        
        // 마지막 점에서 현재 점으로 선을 그리기
        UIGraphicsGetCurrentContext()?.move(to: CGPoint(x: lastPoint.x, y: lastPoint.y))
        UIGraphicsGetCurrentContext()?.addLine(to: CGPoint(x: currPoint.x, y: currPoint.y))
        UIGraphicsGetCurrentContext()?.strokePath() // 선 그리기
        
        // 현재 컨텍스트에서 이미지를 가져와 이미지 뷰에 설정
        imgView.image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext() // 이미지 컨텍스트 종료
        
        lastPoint = currPoint // 현재 점을 마지막 점으로 업데이트
    }
    
    // 터치가 끝날 때 호출되는 메서드
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        UIGraphicsBeginImageContext(imgView.frame.size) // 이미지 컨텍스트 시작
        UIGraphicsGetCurrentContext()?.setStrokeColor(lineColor) // 선 색상 설정
        UIGraphicsGetCurrentContext()?.setLineCap(CGLineCap.round) // 선 끝 모양을 둥글게 설정
        UIGraphicsGetCurrentContext()?.setLineWidth(lineSize) // 선 두께 설정
        
        // 현재 이미지 뷰의 이미지를 컨텍스트에 그리기
        imgView.image?.draw(in: CGRect(x: 0, y: 0, width: imgView.frame.size.width, height: imgView.frame.size.height))
        
        // 마지막 점에서 마지막 점으로 선을 그리기 (실제로는 점을 찍는 것)
        UIGraphicsGetCurrentContext()?.move(to: CGPoint(x: lastPoint.x, y: lastPoint.y))
        UIGraphicsGetCurrentContext()?.addLine(to: CGPoint(x: lastPoint.x, y: lastPoint.y))
        UIGraphicsGetCurrentContext()?.strokePath() // 선 그리기
        
        // 현재 컨텍스트에서 이미지를 가져와 이미지 뷰에 설정
        imgView.image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext() // 이미지 컨텍스트 종료
    }
    
    // 기기가 흔들릴 때 호출되는 메서드
    override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
        if motion == .motionShake { // 흔들림이 감지되면
            imgView.image = nil // 이미지 뷰의 이미지를 nil로 설정하여 지움
        }
    }
}

17-2

import UIKit

// ViewController 클래스 정의 - 그림판 기능 구현
class ViewController: UIViewController {
    // UI 요소 아웃렛 연결
    @IBOutlet var imgView: UIImageView! // 그림을 그릴 이미지 뷰
    @IBOutlet var txtLineSize: UITextField! // 선 두께를 입력하는 텍스트 필드
    
    // 변수 선언
    var lastPoint: CGPoint! // 마지막 터치 위치 저장
    var lineSize: CGFloat = 2.0 // 초기 선 두께
    var lineColor = UIColor.red.cgColor // 초기 선 색상 설정 (빨간색)

    override func viewDidLoad() {
        super.viewDidLoad()
        // 초기 설정 - 선 두께를 텍스트 필드에 표시
        txtLineSize.text = String(Int(lineSize))
    }

    // "Clear" 버튼을 눌렀을 때 이미지 초기화
    @IBAction func btnClearImageView(_ sender: UIButton) {
        imgView.image = nil
    }
    
    // 화면 터치를 시작했을 때 호출되는 함수
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first! as UITouch
        lastPoint = touch.location(in: imgView) // 터치 시작 위치 저장
    }
    
    // 터치가 이동할 때마다 그림을 그리는 함수
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        UIGraphicsBeginImageContext(imgView.frame.size) // 현재 이미지 컨텍스트 생성
        UIGraphicsGetCurrentContext()?.setStrokeColor(lineColor) // 선 색상 설정
        UIGraphicsGetCurrentContext()?.setLineCap(CGLineCap.round) // 선 끝 모양 설정
        UIGraphicsGetCurrentContext()?.setLineWidth(lineSize) // 선 두께 설정
        
        let touch = touches.first! as UITouch
        let currPoint = touch.location(in: imgView) // 현재 터치 위치 가져오기
        
        imgView.image?.draw(in: CGRect(x: 0, y: 0, width: imgView.frame.size.width, height: imgView.frame.size.height)) // 이전 그림 유지
        
        UIGraphicsGetCurrentContext()?.move(to: CGPoint(x: lastPoint.x, y: lastPoint.y)) // 시작 지점 이동
        UIGraphicsGetCurrentContext()?.addLine(to: CGPoint(x: currPoint.x, y: currPoint.y)) // 선 추가
        UIGraphicsGetCurrentContext()?.strokePath() // 경로 그리기
        
        imgView.image = UIGraphicsGetImageFromCurrentImageContext() // 그려진 이미지를 이미지 뷰에 설정
        UIGraphicsEndImageContext() // 컨텍스트 종료
        
        lastPoint = currPoint // 마지막 위치 업데이트
    }
    
    // 터치가 끝났을 때 호출되는 함수
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        UIGraphicsBeginImageContext(imgView.frame.size) // 현재 이미지 컨텍스트 생성
        UIGraphicsGetCurrentContext()?.setStrokeColor(lineColor) // 선 색상 설정
        UIGraphicsGetCurrentContext()?.setLineCap(CGLineCap.round) // 선 끝 모양 설정
        UIGraphicsGetCurrentContext()?.setLineWidth(lineSize) // 선 두께 설정
        
        imgView.image?.draw(in: CGRect(x: 0, y: 0, width: imgView.frame.size.width, height: imgView.frame.size.height)) // 이전 그림 유지
        
        UIGraphicsGetCurrentContext()?.move(to: CGPoint(x: lastPoint.x, y: lastPoint.y)) // 마지막 위치로 이동
        UIGraphicsGetCurrentContext()?.addLine(to: CGPoint(x: lastPoint.x, y: lastPoint.y)) // 한 점에 그리기
        UIGraphicsGetCurrentContext()?.strokePath() // 경로 그리기
        
        imgView.image = UIGraphicsGetImageFromCurrentImageContext() // 그려진 이미지를 이미지 뷰에 설정
        UIGraphicsEndImageContext() // 컨텍스트 종료
    }
    
    // 기기를 흔들었을 때 이미지 초기화
    override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
        if motion == .motionShake {
            imgView.image = nil // 흔들면 이미지 삭제
        }
    }
    
    // 텍스트 필드의 값이 변경되었을 때 선 두께 업데이트
    @IBAction func txtEditChange(_ sender: UITextField) {
        if txtLineSize.text != "" {
            lineSize = CGFloat(Int(txtLineSize.text!)!) // 텍스트 필드의 값을 선 두께로 설정
        }
    }
    
    // 텍스트 필드 편집이 끝났을 때 선 두께 업데이트
    @IBAction func txtDidEndOnExit(_ sender: UITextField) {
        lineSize = CGFloat(Int(txtLineSize.text!)!)
    }
    
    // 텍스트 필드 터치 시 전체 선택
    @IBAction func txtTouchDown(_ sender: UITextField) {
        txtLineSize.selectAll(UITextField.self)
    }
    
    // 버튼을 눌러 선 색상을 검정으로 변경
    @IBAction func btnChangeLineColorBlack(_ sender: UIButton) {
        lineColor = UIColor.black.cgColor
    }
    
    // 버튼을 눌러 선 색상을 빨강으로 변경
    @IBAction func btnChangeLineColorRed(_ sender: UIButton) {
        lineColor = UIColor.red.cgColor
    }
    
    // 버튼을 눌러 선 색상을 초록으로 변경
    @IBAction func btnChangeLineColorGreen(_ sender: UIButton) {
        lineColor = UIColor.green.cgColor
    }
    
    // 버튼을 눌러 선 색상을 파랑으로 변경
    @IBAction func btnChangeLineColorBlue(_ sender: UIButton) {
        lineColor = UIColor.blue.cgColor
    }
}

18 스와이프 제스처 사용하기


19 핀치 제스처 사용해 사진을 확대/축소하기


BMI 앱 만들기

출처: SmileHan - iOS 기초 프로그래밍

bmi 앱 둘러보기 (App Store)

https://apps.apple.com/us/app/bmi-calculator-weight-loss/id666822519?platform=iphone&see-all=customers-also-bought-apps

GPT를 활용한 iOS 용 BMI 앱에 대한 기획서 작성 (기존의 인기 앱들 참고)

  1. 앱 개요
    앱 이름: BMI 계산기
    목적: 사용자가 자신의 신체질량지수(BMI)를 쉽게 계산하고, 결과에 따른 건강 정보와 목표 체중을 제안하여 건강 관리에 도움을 줌.
    대상 사용자: 건강 관리와 체중 조절에 관심 있는 일반 사용자
  2. 주요 기능
    2.1 BMI 계산
    입력 항목: 키, 몸무게
    계산 방법: 입력된 키와 몸무게를 통해 BMI 계산 후, 사용자가 속한 BMI 범주(저체중, 정상, 과체중, 비만)를 표시
    결과 표시: BMI 수치와 함께 각 BMI 범주에 따라 다른 배경 색상 또는 아이콘을 제공하여 직관적으로 시각화
    2.2 목표 BMI 및 체중 설정
    목표 BMI: 사용자가 희망하는 BMI 범위를 설정하면 목표 체중을 제안
    목표 체중 설정: 목표 체중을 설정할 수 있고, 현재 체중과의 차이를 보여줌
    목표 도달 예측: 입력된 목표 체중에 따라, 매일 일정한 체중 감량 시 필요한 예상 기간을 제공
    2.3 건강 정보
    BMI 범주별 건강 정보: 각 BMI 범주에 따른 건강 위험성, 필요한 체중 조절 방법(운동, 식단 등)에 관한 간단한 정보 제공
    추천 운동 및 식단: 과체중, 비만 범주의 사용자에게 가벼운 운동과 식단 예시를 제공하여 목표 달성에 도움
    건강 관련 리소스 링크: 관련 건강 웹사이트, 유튜브 운동 영상 링크 등을 제공
    2.4 기록 및 분석
    체중/BMI 변화 그래프: 체중 및 BMI 변화를 주간, 월간 그래프로 시각화하여 진행 상황을 확인
    기록 기능: 사용자가 매일/주간 체중 및 BMI를 기록할 수 있는 기능
    알림 기능: 사용자에게 주기적인 체크인 알림 전송 (예: 주간 체중 기록 알림)
    2.5 사용자 경험 (UI/UX)
    직관적인 입력 및 결과 화면: 키, 몸무게 입력이 쉽고 계산 결과를 한눈에 확인 가능
    친근한 디자인: BMI 범주에 따른 다양한 아이콘 또는 캐릭터 사용으로 시각적 즐거움 제공
    다크 모드 지원: iOS 기본 다크 모드와 연동하여 UI 변경
  3. 부가 기능 (고급 사용자 대상)
    체지방률 계산: 성별, 나이, 신체 정보에 따른 체지방률 계산 기능 추가
    헬스 데이터 연동: Apple Health와 연동하여 자동으로 키와 체중 데이터를 가져옴
    소셜 공유: 사용자가 BMI 결과와 목표 달성 과정을 소셜 미디어에 공유할 수 있도록 지원
  4. 기술적 요구사항
    프레임워크: Swift, UIKit, CoreGraphics (그래프), CoreData (기록 저장)
    외부 연동: Apple Health 연동 (선택 기능)
    알림 기능: 사용자가 설정한 알림 시간에 푸시 알림 전송
  5. 디자인 방향
    직관적이고 심플한 레이아웃: 사용자가 입력과 결과를 바로 확인할 수 있도록 간단한 UI 설계
    컬러 코드: BMI 범주에 따른 배경 색상 구분 (예: 저체중-파란색, 정상-녹색, 과체중-노란색, 비만-빨간색)
    모바일 친화적 아이콘: 간단한 아이콘 사용으로 초보자도 쉽게 이해할 수 있는 인터페이스
  6. 마케팅 전략
    App Store 최적화(ASO): "BMI", "체중 관리", "다이어트" 키워드를 사용하여 검색 최적화
    초기 무료 제공: 프리미엄 기능을 제외한 기본 기능을 무료로 제공
    건강 블로그 및 유튜브와 협업: 건강 및 피트니스 관련 콘텐츠를 제작하여 앱 홍보

BMI 계산에 대한 소스

BMI를 판정하는 calcBMI()함수 정의

if~else를 switch~case로

BMI 계산하는 클래스로 구현


앱의 개발 순서

1. 앱을 개발할 땐 Main.storyboard에서 UI를 디자인

2. 변수: Outlet 설정

3. 함수: Action에 소스코드 작성

4. Connections inspector 로 Outlet과 Action 연결 확인


IB : 인터페이스 빌더(Interface Builder) 에서 사용하는 attribute


IBOutlet과 IBAction 생성 및 확인


이미지 삽입

이미지를 넣을 땐 Assets 목록에 넣고, 1x, 2x, 3x 가 다르게 있는 이유는 아이패드와 같은 다른 디스플레이에서도 사용할 수 있기 때문이다(해상도가 다름)


Tip : 키보드 입력 칸에서 숫자만 입력하도록 개발할 것이라면

키보드 입력을 숫자와 소수점만 사용할 거라면 inspector 창에서 Keyboard Type을 Decimal Pad로 변경하기

0개의 댓글