출처 : 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 부터 보기

//
// 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])
}
}

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

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

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
}
자주 쓰이는 컨트롤러 요소:
UIViewController - 기본 뷰 컨트롤러로, 거의 모든 화면에 사용됨.
UITableViewController - 목록형 데이터를 표시하는 테이블 뷰 컨트롤러.
UICollectionViewController - 그리드 형태의 데이터를 표시하는 컬렉션 뷰 컨트롤러.
UINavigationController - 뷰 간의 계층적 탐색을 위한 네비게이션 바.
UITabBarController - 하단 탭 바를 사용하여 화면 간 전환을 가능하게 함.
자주 쓰이는 UI 요소:
UILabel - 텍스트를 표시하는 레이블.
UIButton - 버튼.
UIImageView - 이미지를 표시하는 이미지 뷰.
UITextField - 사용자 입력을 받는 텍스트 필드.
UIScrollView - 화면을 스크롤할 수 있게 하는 스크롤 뷰.

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

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()
}
}
}



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로 설정하여 지움
}
}
}

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
}
}


출처: SmileHan - iOS 기초 프로그래밍
- 앱 개요
앱 이름: BMI 계산기
목적: 사용자가 자신의 신체질량지수(BMI)를 쉽게 계산하고, 결과에 따른 건강 정보와 목표 체중을 제안하여 건강 관리에 도움을 줌.
대상 사용자: 건강 관리와 체중 조절에 관심 있는 일반 사용자- 주요 기능
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 변경- 부가 기능 (고급 사용자 대상)
체지방률 계산: 성별, 나이, 신체 정보에 따른 체지방률 계산 기능 추가
헬스 데이터 연동: Apple Health와 연동하여 자동으로 키와 체중 데이터를 가져옴
소셜 공유: 사용자가 BMI 결과와 목표 달성 과정을 소셜 미디어에 공유할 수 있도록 지원- 기술적 요구사항
프레임워크: Swift, UIKit, CoreGraphics (그래프), CoreData (기록 저장)
외부 연동: Apple Health 연동 (선택 기능)
알림 기능: 사용자가 설정한 알림 시간에 푸시 알림 전송- 디자인 방향
직관적이고 심플한 레이아웃: 사용자가 입력과 결과를 바로 확인할 수 있도록 간단한 UI 설계
컬러 코드: BMI 범주에 따른 배경 색상 구분 (예: 저체중-파란색, 정상-녹색, 과체중-노란색, 비만-빨간색)
모바일 친화적 아이콘: 간단한 아이콘 사용으로 초보자도 쉽게 이해할 수 있는 인터페이스- 마케팅 전략
App Store 최적화(ASO): "BMI", "체중 관리", "다이어트" 키워드를 사용하여 검색 최적화
초기 무료 제공: 프리미엄 기능을 제외한 기본 기능을 무료로 제공
건강 블로그 및 유튜브와 협업: 건강 및 피트니스 관련 콘텐츠를 제작하여 앱 홍보






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

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