Do it! 스위프트로 아이폰 앱 만들기 입문, 송호정, 이범근 저,이지스퍼블리싱, 2023년 01월 20일
----------------------------------------
02 Hello World 앱 만들며 Xcode에 완벽 적응하기
03 원하는 이미지 화면에 출력하기 - 이미지 뷰
04 데이트 피커 사용해 날짜 선택하기
05 피커 뷰 사용해 원하는 항목 선택하기
06 얼럿 사용해 경고 표시하기
07 웹 뷰로 간단한 웹 브라우저 만들기
08 맵 뷰로 지도 나타내기
09 페이지 이동하기 - 페이지 컨트롤
10 탭 바 컨트롤러 이용해 여러 개의 뷰 넣기
11 내비게이션 컨트롤러 이용해 화면 전환하기
12 테이블 뷰 컨트롤러 이용해 할 일 목록 만들기
13 음악 재생하고 녹음하기
14 비디오 재생 앱 만들기
15 카메라와 포토 라이브러리에서 미디어 가져오기
16 코어 그래픽스로 화면에 그림 그리기
17 탭과 터치 사용해 스케치 앱 만들기
18 스와이프 제스처 사용하기
19 핀치 제스처 사용해 사진을 확대/축소하기
09 페이지 이동하기 - 페이지 컨트롤
import UIKit
// 이미지 파일 이름들을 담은 배열
var images = [ "01.png", "02.png", "03.png", "04.png", "05.png", "06.png" ]
class ViewController: UIViewController {
// 이미지 뷰와 페이지 컨트롤을 IBOutlet으로 연결
@IBOutlet var imgView: UIImageView!
@IBOutlet var pageControl: UIPageControl!
override func viewDidLoad() {
super.viewDidLoad()
// 뷰가 로드될 때 실행되는 코드
// 페이지 컨트롤에 표시할 페이지 수를 이미지 배열의 개수로 설정
pageControl.numberOfPages = images.count
// 현재 페이지를 3으로 설정 (처음에는 3번 페이지로 설정)
pageControl.currentPage = 3
// 페이지 컨트롤의 비활성 페이지 인디케이터 색상을 회색으로 설정
pageControl.pageIndicatorTintColor = UIColor.gray
// 현재 페이지 인디케이터 색상을 검은색으로 설정
pageControl.currentPageIndicatorTintColor = UIColor.black
// 첫 번째 이미지(0번 인덱스)를 이미지 뷰에 표시
imgView.image = UIImage(named: images[0])
}
// 페이지 컨트롤의 페이지가 변경되었을 때 호출되는 액션 메소드
@IBAction func pageChange(_ sender: UIPageControl) {
// 페이지 컨트롤의 현재 페이지에 해당하는 이미지를 이미지 뷰에 표시
imgView.image = UIImage(named: images[pageControl.currentPage])
}
}
10 탭 바 컨트롤러 이용해 여러 개의 뷰 넣기
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func btnMoveImageView(_ sender: UIButton) {
tabBarController?.selectedIndex = 1
}
@IBAction func btnMoveDatePickerView(_ sender: UIButton) {
tabBarController?.selectedIndex = 2
}
}
11 내비게이션 컨트롤러 이용해 화면 전환하기
import UIKit
class ViewController: UIViewController, EditDelegate {
let imgOn = UIImage(named: "lamp_on.png")
let imgOff = UIImage(named: "lamp_off.png")
var isOn = true
@IBOutlet var txMessage: UITextField!
@IBOutlet var imgView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
imgView.image = imgOn
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let editViewController = segue.destination as! EditViewController
if segue.identifier == "editButton" {
editViewController.textWayValue = "segue : use button"
} else if segue.identifier == "editBarButton" {
editViewController.textWayValue = "segue : use Bar button"
}
editViewController.textMessage = txMessage.text!
editViewController.isOn = isOn
editViewController.delegate = self
}
func didMessageEditDone(_ controller: EditViewController, message: String) {
txMessage.text = message
}
func didImageOnOffDone(_ controller: EditViewController, isOn: Bool) {
if isOn {
imgView.image = imgOn
self.isOn = true
} else {
imgView.image = imgOff
self.isOn = false
}
}
}
12 테이블 뷰 컨트롤러 이용해 할 일 목록 만들기
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// UITableView에서 셀을 재사용하기 위해 dequeueReusableCell 메소드를 호출하여 셀을 가져옴
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
// 해당 행의 텍스트 레이블에 데이터를 설정
cell.textLabel?.text = items[(indexPath as NSIndexPath).row]
// 해당 행에 맞는 이미지를 셀의 이미지 뷰에 설정
cell.imageView?.image = UIImage(named: itemsImageFile[(indexPath as NSIndexPath).row])
// 셀을 반환
return cell
}
iOS 앱을 만들 때 가장 많이 사용되는 `UIViewController` 종류는 다음과 같습니다.
각기 다른 목적에 맞게 사용되며, 앱의 다양한 화면과 흐름을 관리하는 데 중요한 역할을 합니다.
1. UIViewController
- 기본적인 뷰 컨트롤러로, 사용자 인터페이스의 모든 종류를 관리할 수 있습니다.
- `UIViewController`는 기본 뷰 컨트롤러 클래스이며, 거의 모든 화면을 만들 때 사용됩니다.
- 버튼, 레이블, 이미지 뷰 등을 추가하여 앱의 기본적인 화면을 구성합니다.
2. UITableViewController
- 리스트 형태의 데이터를 표시하는 데 사용됩니다. `UITableViewController`는 `UITableView`와 관련된 기능을 많이 자동으로 처리해 줍니다.
- 예를 들어, 데이터를 세로로 나열한 리스트 형태로 보여주는 화면에서는 `UITableViewController`를 사용합니다.
- `UITableViewController`는 `UITableViewDataSource`와 `UITableViewDelegate`를 자동으로 설정하여 `UITableView`를 쉽게 다룰 수 있게 해줍니다.
3. UICollectionViewController
- 그리드 형태로 데이터를 표시할 때 사용됩니다. `UICollectionViewController`는 `UICollectionView`를 쉽게 사용할 수 있도록 도와줍니다.
- 예를 들어, 사진 갤러리, 아이템 그리드 등에서 자주 사용됩니다.
- `UICollectionView`는 리스트뿐만 아니라 다양한 형태의 레이아웃을 지원하므로, 복잡한 레이아웃을 구현할 때 유용합니다.
4. UINavigationController
- `UINavigationController`는 **내비게이션 스택**을 관리하는 컨트롤러로, 화면 간의 전환을 쉽게 처리할 수 있게 해줍니다.
- `UINavigationController`는 스택 구조를 이용하여 여러 화면을 계층적으로 탐색할 수 있게 합니다. 각 화면은 `push`로 스택에 쌓이고, `pop`으로 돌아올 수 있습니다.
- 예를 들어, 리스트에서 세부 항목으로 이동하는 경우에 사용됩니다. 이때 내비게이션 바와 함께 제목, 뒤로 가기 버튼 등을 자동으로 관리합니다.
5. UITabBarController
- `UITabBarController`는 **탭 바**를 사용하여 앱의 여러 섹션을 쉽게 전환할 수 있도록 해줍니다.
- 주로 앱의 하단에 탭 바를 두고 여러 화면(예: 홈, 검색, 설정 등)을 전환할 때 사용됩니다.
- 탭을 클릭하여 다른 화면으로 빠르게 전환할 수 있게 하며, 여러 개의 화면을 관리하는 데 유용합니다.
6. UIPageViewController
- `UIPageViewController`는 페이지 기반의 화면 전환을 관리하는 데 사용됩니다. 페이지를 넘기듯 화면을 전환할 수 있게 해줍니다.
- 주로 페이지 뷰 형식으로 화면을 전환할 때 사용됩니다. 예를 들어, 페이지가 넘겨지는 방식으로 이미지나 내용을 보여주는 화면에서 사용됩니다.
7. UIAlertController
- `UIAlertController`는 알림(Alert) 또는 액션 시트(Action Sheet) 를 표시하는 데 사용됩니다.
- 예를 들어, 사용자에게 중요한 메시지를 보여주거나, 선택을 요구하는 버튼을 제공하는 등의 경우에 사용됩니다.
8. UISplitViewController
- `UISplitViewController`는 두 개의 뷰를 나누어 보여주는 컨트롤러입니다. 주로 아이패드에서 많이 사용되며, 큰 화면에서 여러 뷰를 동시에 보여줄 때 유용합니다.
- 일반적으로 하나의 뷰는 마스터 뷰로, 다른 하나는 디테일 뷰로 사용됩니다. 예를 들어, 왼쪽에는 목록이, 오른쪽에는 상세 내용이 표시되는 형식입니다.
9. MKMapViewController (혹은 MKMapView)
- `MKMapViewController`는 지도를 표시하고 상호작용할 수 있는 뷰를 제공합니다.
- 주로 위치 기반 서비스와 관련된 앱에서 사용됩니다. 예를 들어, 위치 검색, 마커 추가, 현재 위치 표시 등과 관련된 화면을 구현할 때 사용됩니다.
10. UIActivityViewController
- `UIActivityViewController`는 공유 기능을 제공하는 컨트롤러입니다.
- 예를 들어, 사용자에게 사진, 텍스트, 웹 링크 등을 다른 앱과 공유할 수 있는 기능을 제공할 때 사용됩니다.
---
가장 많이 사용되는 `UIViewController`는?
1. `UIViewController`– 거의 모든 앱에서 기본 화면을 관리하는 컨트롤러로 사용됩니다.
2. `UITableViewController` – 리스트 기반 UI를 많이 사용하는 앱에서 사용됩니다. (e.g. 연락처, 뉴스 피드, 이메일 등)
3. `UINavigationController` – 여러 화면 간에 내비게이션을 처리할 때 사용됩니다.
4. `UITabBarController` – 앱의 여러 섹션을 탭으로 전환하는 앱에서 널리 사용됩니다.
이 외에도 `UICollectionViewController`, `UIPageViewController` 등도 많이 사용되지만, 기본적으로 앱에서 **목록 표시, 화면 전환, 탭**을 담당하는 컨트롤러들이 가장 흔하게 사용됩니다.
13 음악 재생하고 녹음하기
//
// ViewController.swift
// Audio
//
// Created by BeomGeun Lee on 2021.
//
import UIKit // UIKit 프레임워크를 가져옴
import AVFoundation // 오디오 재생 및 녹음을 위한 AVFoundation 프레임워크를 가져옴
class ViewController: UIViewController, AVAudioPlayerDelegate, AVAudioRecorderDelegate { // UIViewController를 상속하고 AVAudioPlayerDelegate 및 AVAudioRecorderDelegate 프로토콜을 채택
var audioPlayer: AVAudioPlayer! // 오디오 재생을 위한 AVAudioPlayer 인스턴스
var audioFile: URL! // 오디오 파일의 URL
let MAX_VOLUME: Float = 10.0 // 최대 볼륨 상수
var progressTimer: Timer! // 재생 상태 업데이트를 위한 타이머
// 타이머 업데이트 메서드를 위한 선택자
let timePlayerSelector: Selector = #selector(ViewController.updatePlayTime)
let timeRecordSelector: Selector = #selector(ViewController.updateRecordTime)
// UI 요소 IBOutlet 연결
@IBOutlet var pvProgressPlay: UIProgressView! // 재생 진행 상태를 표시하는 UIProgressView
@IBOutlet var lblCurrentTime: UILabel! // 현재 재생 시간을 표시하는 UILabel
@IBOutlet var lblEndTime: UILabel! // 오디오의 총 시간을 표시하는 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! // 녹음 시간을 표시하는 UILabel
var audioRecorder: AVAudioRecorder! // 오디오 녹음을 위한 AVAudioRecorder 인스턴스
var isRecordMode = false // 녹음 모드 여부를 나타내는 플래그
override func viewDidLoad() { // 뷰가 로드된 후 호출되는 메서드
super.viewDidLoad() // 부모 클래스의 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") // 번들에서 오디오 파일 URL 가져오기
} else { // 녹음 모드일 경우
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] // 문서 디렉토리 경로 가져오기
audioFile = documentDirectory.appendingPathComponent("recordFile.m4a") // 녹음 파일 URL 설정
}
}
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) // AVAudioRecorder 인스턴스 생성
} 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 AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default) // 세션 카테고리 설정
try AVAudioSession.sharedInstance().setActive(true) // 세션 활성화
} catch let error as NSError {
print("Error-setCategory: \(error)") // 오류 발생 시 출력
}
do {
try session.setActive(true) // 세션 활성화
} catch let error as NSError {
print("Error-setActive: \(error)") // 오류 발생 시 출력
}
}
func initPlay() { // 재생 초기화 메서드
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioFile) // AVAudioPlayer 인스턴스 생성
} 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 { // NSTimeInterval을 문자열로 변환하는 메서드
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) { // 오디오 재생 완료 시 호출
14 비디오 재생 앱 만들기
import UIKit
import AVKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 뷰가 로드된 후 추가 설정을 위한 곳
// 현재는 초기화나 설정 작업이 필요 없으므로 비워둡니다.
}
// 내부에 저장된 비디오 파일을 재생하는 버튼 액션
@IBAction func btnPlayInternalMovie(_ sender: UIButton) {
// 앱 번들 안에 저장된 mp4 파일의 경로를 가져옵니다.
let filePath:String? = Bundle.main.path(forResource: "FastTyping", ofType: "mp4")
// 경로가 유효하다면 NSURL 객체로 변환
if let filePath = filePath {
let url = NSURL(fileURLWithPath: filePath) // NSURL 객체로 URL 생성
playVideo(url: url) // 비디오 재생 함수 호출
} else {
print("File not found") // 파일 경로가 없다면 에러 메시지 출력
}
}
// 외부 URL에서 비디오를 재생하는 버튼 액션
@IBAction func btnPlayerExternalMovie(_ sender: UIButton) {
// 외부 URL에서 mp4 파일을 가져옵니다.
let url = NSURL(string: "https://dl.dropboxusercontent.com/s/e38auz050w2mvud/Fireworks.mp4")!
// 비디오 재생 함수 호출
playVideo(url: url)
}
// 비디오 URL을 받아서 AVPlayerViewController를 사용하여 비디오를 재생하는 함수
private func playVideo(url: NSURL) {
let playerController = AVPlayerViewController() // 비디오 플레이어 컨트롤러 생성
// AVPlayer 인스턴스를 생성하여 URL로 비디오를 로드
let player = AVPlayer(url: url as URL)
playerController.player = player // AVPlayer를 AVPlayerViewController에 설정
// 비디오 플레이어 화면을 현재 화면에 띄운 후, 재생 시작
self.present(playerController, animated: true) {
player.play() // 비디오 재생 시작
}
}
}
15 카메라와 포토 라이브러리에서 미디어 가져오기
import UIKit
import MobileCoreServices
class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
@IBOutlet var imgView: UIImageView!
let imagePicker: UIImagePickerController! = UIImagePickerController()
var captureImage: UIImage!
var videoURL: URL!
var flagImageSave = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func btnCaptureImageFromCamera(_ sender: UIButton) {
if (UIImagePickerController.isSourceTypeAvailable(.camera)) {
flagImageSave = true
imagePicker.delegate = self
imagePicker.sourceType = .camera
imagePicker.mediaTypes = ["public.image"]
imagePicker.allowsEditing = false
present(imagePicker, animated: true, completion: nil)
}
else {
myAlert("Camera inaccessable", message: "Application cannot access the camera.")
}
}
@IBAction func btnLoadImageFromLibrary(_ sender: UIButton) {
if (UIImagePickerController.isSourceTypeAvailable(.photoLibrary)) {
flagImageSave = false
imagePicker.delegate = self
imagePicker.sourceType = .photoLibrary
imagePicker.mediaTypes = ["public.image"]
imagePicker.allowsEditing = true
present(imagePicker, animated: true, completion: nil)
}
else {
myAlert("Photo album inaccessable", message: "Application cannot access the photo album.")
}
}
@IBAction func btnRecordVideoFromCamera(_ sender: UIButton) {
if (UIImagePickerController.isSourceTypeAvailable(.camera)) {
flagImageSave = true
imagePicker.delegate = self
imagePicker.sourceType = .camera
imagePicker.mediaTypes = ["public.movie"]
imagePicker.allowsEditing = false
present(imagePicker, animated: true, completion: nil)
}
else {
myAlert("Camera inaccessable", message: "Application cannot access the camera.")
}
}
@IBAction func btnLoadVideoFromLibrary(_ sender: UIButton) {
if (UIImagePickerController.isSourceTypeAvailable(.photoLibrary)) {
flagImageSave = false
imagePicker.delegate = self
imagePicker.sourceType = .photoLibrary
imagePicker.mediaTypes = ["public.movie"]
imagePicker.allowsEditing = false
present(imagePicker, animated: true, completion: nil)
}
else {
myAlert("Photo album inaccessable", message: "Application cannot access the photo album.")
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as! NSString
if mediaType.isEqual(to: "public.image" as String) {
captureImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
if flagImageSave {
UIImageWriteToSavedPhotosAlbum(captureImage, self, nil, nil)
}
imgView.image = captureImage
}
else if mediaType.isEqual(to: "public.movie" as String) {
if flagImageSave {
videoURL = (info[UIImagePickerController.InfoKey.mediaURL] as! URL)
UISaveVideoAtPathToSavedPhotosAlbum(videoURL.relativePath, self, nil, nil)
}
}
self.dismiss(animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.dismiss(animated: true, completion: nil)
}
func myAlert(_ title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert)
let action = UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
}
16 코어 그래픽스로 화면에 그림 그리기
import UIKit
class ViewController: UIViewController {
@IBOutlet var imgView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func btnDrawLine(_ sender: UIButton) {
UIGraphicsBeginImageContext(imgView.frame.size)
let context = UIGraphicsGetCurrentContext()!
// Draw Line
context.setLineWidth(2.0)
context.setStrokeColor(UIColor.red.cgColor)
context.move(to: CGPoint(x: 70, y: 50))
context.addLine(to: CGPoint(x: 270, y: 250))
context.strokePath()
// Draw Triangle
context.setLineWidth(4.0)
context.setStrokeColor(UIColor.blue.cgColor)
context.move(to: CGPoint(x: 170, y: 200))
context.addLine(to: CGPoint(x: 270, y: 350))
context.addLine(to: CGPoint(x: 70, y: 350))
context.addLine(to: CGPoint(x: 170, y: 200))
context.strokePath()
imgView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
@IBAction func btnDrawRectangle(_ sender: UIButton) {
UIGraphicsBeginImageContext(imgView.frame.size)
let context = UIGraphicsGetCurrentContext()!
// Draw Rectangle
context.setLineWidth(2.0)
context.setStrokeColor(UIColor.red.cgColor)
context.addRect(CGRect(x: 70, y: 100, width: 200, height: 200))
context.strokePath()
imgView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
@IBAction func btnDrawCircle(_ sender: UIButton) {
UIGraphicsBeginImageContext(imgView.frame.size)
let context = UIGraphicsGetCurrentContext()!
// Draw Ellipse
context.setLineWidth(2.0)
context.setStrokeColor(UIColor.red.cgColor)
context.addEllipse(in: CGRect(x: 70, y: 50, width: 200, height: 100))
context.strokePath()
// Draw Circle
context.setLineWidth(5.0)
context.setStrokeColor(UIColor.green.cgColor)
context.addEllipse(in: CGRect(x: 70, y: 200, width: 200, height: 200))
context.strokePath()
imgView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
@IBAction func btnDrawArc(_ sender: UIButton) {
UIGraphicsBeginImageContext(imgView.frame.size)
let context = UIGraphicsGetCurrentContext()!
// Draw Arc
context.setLineWidth(5.0)
context.setStrokeColor(UIColor.red.cgColor)
context.move(to: CGPoint(x: 100, y: 50))
context.addArc(tangent1End: CGPoint(x: 250, y:50), tangent2End: CGPoint(x:250, y:200), radius: CGFloat(50))
context.addLine(to: CGPoint(x: 250, y: 200))
context.move(to: CGPoint(x: 100, y: 250))
context.addArc(tangent1End: CGPoint(x: 270, y:250), tangent2End: CGPoint(x:100, y:400), radius: CGFloat(20))
context.addLine(to: CGPoint(x: 100, y: 400))
context.strokePath()
imgView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
@IBAction func btnDrawFill(_ sender: UIButton) {
UIGraphicsBeginImageContext(imgView.frame.size)
let context = UIGraphicsGetCurrentContext()!
// Draw Rectangle
context.setLineWidth(1.0)
context.setStrokeColor(UIColor.red.cgColor)
context.setFillColor(UIColor.red.cgColor)
let rectangel = CGRect(x: 70, y: 50, width: 200, height: 100)
context.addRect(rectangel)
context.fill(rectangel)
context.strokePath()
// Draw Circle
context.setLineWidth(1.0)
context.setStrokeColor(UIColor.blue.cgColor)
context.setFillColor(UIColor.blue.cgColor)
let circle = CGRect(x: 70, y: 200, width: 200, height: 100)
context.addEllipse(in: circle)
context.fillEllipse(in: circle)
context.strokePath()
// Draw Triangle
context.setLineWidth(1.0)
context.setStrokeColor(UIColor.green.cgColor)
context.setFillColor(UIColor.green.cgColor)
context.move(to: CGPoint(x: 170, y: 350))
context.addLine(to: CGPoint(x: 270, y: 450))
context.addLine(to: CGPoint(x: 70, y: 450))
context.addLine(to: CGPoint(x: 170, y: 350))
context.fillPath()
context.strokePath()
imgView.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
17 탭과 터치 사용해 스케치 앱 만들기
import UIKit
class ViewController: UIViewController {
@IBOutlet var imgView: UIImageView! // 그림을 그릴 이미지 뷰
var lastPoint: CGPoint! // 마지막 터치 위치를 저장
var lineSize: CGFloat = 5.0 // 선의 크기 (기본값 5.0)
var lineColor = UIColor.black.cgColor // 선의 색 (기본값 검정색)
override func viewDidLoad() {
super.viewDidLoad()
// 추가적인 설정이 필요할 경우 이곳에서 초기화 작업을 할 수 있습니다.
}
// '지우기' 버튼 액션
@IBAction func btnClearImageView(_ sender: UIButton) {
// 이미지 뷰의 이미지를 지웁니다. 즉, 그림판을 초기화합니다.
imgView.image = nil
}
// 사용자가 화면을 터치하기 시작할 때 호출되는 메소드
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first! as UITouch // 첫 번째 터치 포인트를 가져옵니다.
// 터치 위치를 lastPoint에 저장하여, 그리기 시작 시 참고합니다.
lastPoint = touch.location(in: imgView)
}
// 사용자가 화면을 터치하면서 이동할 때 호출되는 메소드
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
// 그리기 컨텍스트를 시작하여 이미지를 업데이트합니다.
UIGraphicsBeginImageContext(imgView.frame.size)
// 현재 컨텍스트의 속성을 설정합니다 (선 색, 선 끝 모양, 선 두께).
UIGraphicsGetCurrentContext()?.setStrokeColor(lineColor) // 선 색
UIGraphicsGetCurrentContext()?.setLineCap(.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() // 경로를 그려서 선을 완성
// 그린 이미지를 UIImage로 변환하여 imgView에 설정
imgView.image = UIGraphicsGetImageFromCurrentImageContext()
// 그리기 작업이 끝났으므로 그래픽 컨텍스트를 종료합니다.
UIGraphicsEndImageContext()
// 마지막 터치 위치를 업데이트하여, 그리기 작업이 계속 이어질 수 있도록 합니다.
lastPoint = currPoint
}
// 사용자가 화면에서 손을 뗄 때 호출되는 메소드
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
// 터치가 끝났을 때 그리기 작업을 마무리하기 위한 컨텍스트 생성
UIGraphicsBeginImageContext(imgView.frame.size)
// 그리기 속성 설정 (선 색, 선 끝 모양, 선 두께)
UIGraphicsGetCurrentContext()?.setStrokeColor(lineColor)
UIGraphicsGetCurrentContext()?.setLineCap(.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
}
}
}
소스 개선하기
import UIKit
class ViewController: UIViewController {
@IBOutlet var imgView: UIImageView! // 사용자 그림을 그릴 이미지 뷰
var lastPoint: CGPoint! // 마지막 터치 위치
var lineSize: CGFloat = 5.0 // 선의 두께
var lineColor = UIColor.black.cgColor // 선의 색상
override func viewDidLoad() {
super.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) // 마지막 터치 위치 저장
}
// 터치가 이동할 때 호출되는 메서드
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 // 흔들림이 감지되면 이미지 뷰를 지움
}
}
}
https://www.apple.com/kr/search/bmi?src=globalnav
bmi - Apple (KR)
www.apple.com
기존 앱스토어에 있는 BMI앱 분석하기
이 소스 코드에서는 iOS 개발에 필요한 몇 가지 중요한 문법과 기능들이 포함되어 있습니다.
이들을 정리하여 설명드리겠습니다. 각 기능이 어떻게 동작하는지, 그리고 왜 중요한지에 대해 알아보겠습니다.
### 1. **IBOutlet & IBAction**
**IBOutlet**과 **IBAction**은 인터페이스 빌더에서 UI 요소와 코드와의 연결을 위해 사용하는 중요한 키워드입니다.
- **IBOutlet**: UI 요소(버튼, 레이블, 텍스트 필드 등)와 코드 간의 연결을 생성하는 데 사용됩니다. `@IBOutlet`은 Interface Builder에서 해당 UI 요소를 코드와 연결할 때 사용됩니다.
```swift
@IBOutlet weak var resultLabel: UILabel! // 레이블을 IBOutlet으로 선언
```
- **IBAction**: UI 요소의 액션(예: 버튼 클릭)과 코드를 연결하는 데 사용됩니다. `@IBAction`은 UI 요소에서 발생하는 이벤트(버튼 클릭 등)가 트리거되었을 때 호출되는 메서드를 정의합니다.
```swift
@IBAction func calculateBMI(_ sender: UIButton) { // 버튼 클릭 시 호출되는 함수
// 함수 내용
}
```
### 2. **guard 문**
**`guard`** 문은 조건이 참일 때만 코드 블록을 실행하도록 하는 조건문입니다. 주로 함수나 메서드 초기에 유효성 검사를 할 때 사용됩니다. 조건이 거짓일 경우 `else` 블록에서 특정 작업을 수행하고 함수에서 빠져나가게 됩니다.
```swift
guard let heightText = heightTextField.text, let weightText = weightTextField.text,
let height = Double(heightText), let weight = Double(weightText) else {
resultLabel.text = "잘못된 입력입니다. 다시 시도해주세요."
return
}
```
- 이 코드는 `heightTextField`와 `weightTextField`에서 입력 받은 텍스트가 `Double`로 변환될 수 있는지 확인합니다. 변환이 실패하면 사용자에게 "잘못된 입력입니다"라는 메시지를 보여주고, 이후 코드 실행을 멈추도록 합니다.
### 3. **UserDefaults**
**`UserDefaults`**는 사용자 데이터를 로컬에 간단하게 저장하고 불러오는 데 사용하는 클래스입니다. 앱을 종료해도 저장된 데이터는 유지됩니다.
```swift
// BMI를 저장
UserDefaults.standard.set(savedBMI, forKey: "SavedBMI")
// 저장된 BMI를 불러오기
let savedBMI = UserDefaults.standard.array(forKey: "SavedBMI") as? [Double] ?? []
```
- `UserDefaults.standard.set(...)`으로 데이터를 저장하고, `UserDefaults.standard.array(forKey: ...)`로 데이터를 불러옵니다. 이 방식은 간단한 데이터(문자열, 숫자 등) 저장에 유용합니다.
### 4. **String Formatting**
**String formatting**은 숫자나 다른 값을 문자열로 변환할 때, 특정 형식을 지정하는 방법입니다. 예를 들어, BMI 값을 소수점 이하 두 자리로 표시할 때 사용합니다.
```swift
resultLabel.text = "BMI: \(String(format: "%.2f", bmi))"
```
- `String(format: "%.2f", bmi)`는 `bmi` 값을 소수점 이하 2자리로 포맷합니다. `%.2f`는 부동 소수점 숫자를 2자리로 출력하라는 형식 지정자입니다.
### 5. **조건문 (if, else if, else)**
조건문은 코드 흐름을 제어하는 데 필수적인 문법입니다. 특정 조건에 맞는 경우에만 특정 작업을 수행하도록 할 때 사용합니다.
```swift
if bmi < 18.5 {
status = "저체중"
advice = "체중을 늘리기 위한 식단과 운동을 고려해보세요."
} else if bmi >= 18.5 && bmi < 24.9 {
status = "정상 체중"
advice = "건강한 체중을 유지하고 있습니다!"
} else if bmi >= 25 && bmi < 29.9 {
status = "과체중"
advice = "건강한 체중을 유지하기 위해 운동과 식단 조절이 필요합니다."
} else {
status = "비만"
advice = "비만입니다. 체중을 줄이기 위한 전문가 상담을 고려하세요."
}
```
- 이 코드는 `bmi` 값에 따라 상태를 구분하고 그에 맞는 권장사항을 제공하는 예입니다. `if`, `else if`, `else`는 여러 조건을 순차적으로 비교하여 처리합니다.
### 6. **UITextField**
**`UITextField`**는 사용자가 텍스트를 입력할 수 있는 UI 요소입니다. 여기서는 BMI 계산에 필요한 키와 체중을 입력 받는 데 사용됩니다.
```swift
@IBOutlet weak var heightTextField: UITextField!
@IBOutlet weak var weightTextField: UITextField!
```
- `UITextField`의 `.text` 프로퍼티를 통해 사용자가 입력한 텍스트를 읽어오고, 이를 변환하여 숫자 계산을 진행합니다.
### 7. **IBAction에 매개변수 전달**
**`IBAction`** 메서드는 버튼 등 UI 요소에서 발생한 이벤트를 처리하는 메서드입니다. `sender` 매개변수는 해당 이벤트를 발생시킨 UI 요소를 가리킵니다. 이를 통해, 버튼의 종류나 상태에 따른 처리도 가능합니다.
```swift
@IBAction func calculateBMI(_ sender: UIButton) {
// sender는 이벤트를 발생시킨 UIButton 객체입니다.
}
```
- 위 코드에서 `sender`는 클릭된 버튼 객체를 나타냅니다. 필요에 따라 버튼의 제목을 확인하거나 다른 작업을 수행할 수 있습니다.
### 8. **UILabel을 통한 UI 업데이트**
**`UILabel`**은 사용자에게 정보를 출력하는 데 사용되는 UI 요소입니다. `resultLabel.text`처럼 텍스트를 업데이트할 수 있습니다.
```swift
resultLabel.text = "BMI: \(String(format: "%.2f", bmi))"
```
- 이 코드는 `resultLabel`의 텍스트를 업데이트하여 BMI 계산 결과를 사용자에게 보여줍니다.
### 9. **배열과 String 합치기**
**배열**과 **문자열 합치기**를 통해 여러 값을 하나의 문자열로 만들 수 있습니다. 예를 들어, 저장된 BMI 값들을 하나의 문자열로 결합할 때 사용됩니다.
```swift
savedBMIListLabel.text = "저장된 BMI:\n" + savedBMI.map { String(format: "%.2f", $0) }.joined(separator: "\n")
```
- `map`은 배열의 각 요소를 변환하는 함수입니다. 이 예에서는 `savedBMI` 배열의 각 값을 소수점 2자리로 포맷한 뒤, 각 BMI 값을 `\n`으로 구분하여 하나의 문자열로 합칩니다.
---
### 요약
이 코드를 통해 **iOS 기본 UI 구성**, **사용자 입력 처리**, **조건문과 데이터 저장**, **UI 업데이트** 등의 중요한 기능을 다루고 있습니다. 각 문법을 차근차근 익히면 iOS 앱 개발에 필요한 기본적인 기술을 쌓을 수 있습니다. 여기서 다룬 주요 개념을 정리하면:
- **IBOutlet & IBAction**: UI 요소와 코드 연결
- **guard**: 조건 체크 및 조기 리턴
- **UserDefaults**: 간단한 데이터 저장
- **String formatting**: 문자열 형식화
- **if-else 조건문**: 조건에 따른 분기
- **UITextField**: 사용자 입력 받기
- **UILabel**: 텍스트 업데이트
- **배열 처리 및 문자열 결합**: 데이터를 배열로 처리하고, 문자열로 변환하기
이 기능들을 차근차근 학습하고 실제 앱에 적용해보면, iOS 개발에 중요한 기초를 다질 수 있습니다.
BMI 계산
let weight = 60.0
let height = 170.0
let bmi = weight / (height*height*0.0001) // kg/m*m
print(bmi)
import Foundation
let weight = 60.0
let height = 170.0
let bmi = weight / (height*height*0.0001) // kg/m*m
let shortenedBmi = String(format: "%.1f", bmi)
var body = ""
if bmi >= 40 {
body = "3단계 비만"
} else if bmi >= 30 && bmi < 40 {
body = "2단계 비만"
} else if bmi >= 25 && bmi < 30 {
body = "1단계 비만"
} else if bmi >= 18.5 && bmi < 25 {
body = "정상"
} else {
body = "저체중"
}
print("BMI:\(shortenedBmi), 판정:\(body)")
앱 개발 절차
UI 디자인 확인 > 변수 : Outlet를 잡기 > Connections inspector로 한번만 연결된 것인지 확인 > Action에 함수 '소스 코드' 작성
클래스의 프로퍼티 선언에 IBOutlet와 IBInspectable 사용
클래스의 메서드 선언에 IBAction과 IBSegueAction 사용
키보드 종류
'iOS 프로그래밍' 카테고리의 다른 글
iOS 프로그래밍_11주차 (0) | 2024.11.27 |
---|---|
iOS 프로그래밍_10주차 (0) | 2024.11.20 |
iOS 프로그래밍_8주차 (0) | 2024.11.06 |
iOS 프로그래밍_7주차 (0) | 2024.10.30 |
iOS 프로그래밍_6주차 (0) | 2024.10.16 |