코드 분석
→ 앱 디자인 및 키보드 설정
오픈소스로 있는 코드의 실행화면과 코드를 확인해볼거에요.
Page Control
: 페이지 이동하기 - 페이지 컨트롤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])
}
}
Tab Bar
: 탭 바 컨트롤러 이용해 여러 개의 뷰 넣기Navigation
: 내비게이션 컨트롤러 이용해 화면 전환하기import UIKit
// ViewController는 UIViewController를 상속받고, EditDelegate 프로토콜을 채택함
class ViewController: UIViewController, EditDelegate {
// 조명 켜짐/꺼짐 상태에 따라 사용할 이미지 파일을 선언
let imgOn = UIImage(named: "lamp_on.png") // 조명이 켜진 상태 이미지
let imgOff = UIImage(named: "lamp_off.png") // 조명이 꺼진 상태 이미지
var isOn = true // 조명의 상태 (기본적으로 켜짐 상태로 설정)
// IBOutlet을 사용하여 스토리보드에서 UI 요소에 연결
@IBOutlet var txMessage: UITextField! // 텍스트 입력 필드 (메시지 입력용)
@IBOutlet var imgView: UIImageView! // 이미지 뷰 (조명 이미지 표시용)
// 화면이 로드될 때 호출되는 메서드
override func viewDidLoad() {
super.viewDidLoad()
// 추가적인 초기화 작업이 있을 경우 여기에 작성
imgView.image = imgOn // 화면에 조명이 켜진 상태의 이미지를 표시
}
// segue가 발생하기 전에 호출되는 메서드
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// EditViewController로 segue를 통해 전환될 때 실행되는 코드
let editViewController = segue.destination as! EditViewController
// segue의 식별자를 확인하여 어떤 방식으로 전환되었는지 구분
if segue.identifier == "editButton" {
editViewController.textWayValue = "segue : use button" // 버튼을 통해 이동한 경우
} else if segue.identifier == "editBarButton" {
editViewController.textWayValue = "segue : use Bar button" // 바 버튼을 통해 이동한 경우
}
// 현재 ViewController에서 입력된 텍스트와 조명 상태를 EditViewController로 전달
editViewController.textMessage = txMessage.text! // 메시지 텍스트
editViewController.isOn = isOn // 조명의 켜짐/꺼짐 상태
editViewController.delegate = self // EditViewController가 ViewController의 delegate를 참조하도록 설정
}
// EditViewController에서 메시지가 수정 완료되면 호출되는 delegate 메서드
func didMessageEditDone(_ controller: EditViewController, message: String) {
txMessage.text = message // 전달받은 메시지로 텍스트 필드를 업데이트
}
// EditViewController에서 조명의 상태(켜짐/꺼짐)가 변경되면 호출되는 delegate 메서드
func didImageOnOffDone(_ controller: EditViewController, isOn: Bool) {
if isOn {
imgView.image = imgOn // 조명이 켜진 상태로 이미지 변경
self.isOn = true // 조명이 켜졌다고 상태 변경
} else {
imgView.image = imgOff // 조명이 꺼진 상태로 이미지 변경
self.isOn = false // 조명이 꺼졌다고 상태 변경
}
}
}
Table
: 테이블 뷰 컨트롤러 이용해 할 일 목록 만들기import UIKit
// 데이터 항목 목록과 해당 항목에 대응하는 이미지 파일 이름 목록
var items = ["책 구매", "철수와 약속", "스터디 준비하기"]
var itemsImageFile = ["cart.png", "clock.png", "pencil.png"]
class TableViewController: UITableViewController {
// 테이블 뷰 UI 요소 연결
@IBOutlet var tvListView: UITableView!
// 뷰가 로드된 후 호출되는 메서드
override func viewDidLoad() {
super.viewDidLoad()
// 내비게이션 바에 '편집' 버튼을 추가
self.navigationItem.leftBarButtonItem = self.editButtonItem
}
// 뷰가 화면에 나타날 때마다 테이블 뷰 데이터를 새로 고침
override func viewWillAppear(_ animated: Bool) {
tvListView.reloadData()
}
// MARK: - Table view data source
// 섹션 수를 반환
override func numberOfSections(in tableView: UITableView) -> Int {
return 1 // 하나의 섹션만 사용
}
// 주어진 섹션에 포함된 행의 개수를 반환
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count // 'items' 배열의 개수만큼 행을 생성
}
// 각 행에 표시할 셀을 반환
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 셀을 재사용 큐에서 꺼내서 설정
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
}
// MARK: - Table view editing support
// 테이블 뷰의 특정 행을 삭제할 수 있도록 설정
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// 데이터 소스에서 항목과 이미지를 삭제하고, 테이블 뷰에서 해당 행 삭제
items.remove(at: (indexPath as NSIndexPath).row)
itemsImageFile.remove(at: (indexPath as NSIndexPath).row)
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// 항목 삽입을 위한 로직 (추가 작업 필요)
}
}
// 삭제 버튼의 텍스트를 "삭제"로 설정
override func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
return "삭제"
}
// MARK: - Rearranging support
// 테이블 뷰에서 행을 이동할 수 있도록 설정
override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
// 이동할 항목과 해당 이미지 가져오기
let itemToMove = items[(fromIndexPath as NSIndexPath).row]
let itemImageToMove = itemsImageFile[(fromIndexPath as NSIndexPath).row]
// 원본 위치에서 항목과 이미지 삭제
items.remove(at: (fromIndexPath as NSIndexPath).row)
itemsImageFile.remove(at: (fromIndexPath as NSIndexPath).row)
// 새 위치에 항목과 이미지 삽입
items.insert(itemToMove, at: (to as NSIndexPath).row)
itemsImageFile.insert(itemImageToMove, at: (to as NSIndexPath).row)
}
// MARK: - Navigation
// 스토리보드에서 segue를 통해 다른 뷰로 전환될 때 데이터를 준비하는 메서드
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "sgDetail" {
// 선택된 셀을 가져와서, 그 셀에 해당하는 인덱스 경로를 찾음
let cell = sender as! UITableViewCell
let indexPath = self.tvListView.indexPath(for: cell)
// 세그웨이를 통해 넘겨받은 DetailViewController로 데이터를 전달
let detailView = segue.destination as! DetailViewController
detailView.reciveItem(items[((indexPath! as NSIndexPath).row)]) // 선택된 항목의 텍스트 전달
}
}
}
Audio
: 음악 재생하고 녹음하기import UIKit
import AVFoundation
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()
// 녹음 모드가 아닌 경우: 오디오 플레이어 초기화 및 녹음 관련 UI 비활성화
if !isRecordMode {
initPlay()
btnRecord.isEnabled = false
lblRecordTime.isEnabled = false
} else {
// 녹음 모드인 경우: 녹음 초기화
initRecord()
}
}
// 오디오 파일을 선택하는 메서드
func selectAudioFile() {
// 녹음 모드가 아니면 "Sicilian_Breeze.mp3" 파일을 사용
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 {
// AVAudioRecorder 객체 생성
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 AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
} catch let error as NSError {
print("Error-setCategory : \(error)")
}
}
// 오디오 플레이어 초기화 설정
func initPlay() {
do {
// AVAudioPlayer 객체 생성
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
}
// NSTimeInterval을 "mm:ss" 형식의 문자열로 변환
func convertNSTimeInterval2String(_ time: TimeInterval) -> String {
let min = Int(time / 60)
let sec = Int(time.truncatingRemainder(dividingBy: 60))
return String(format: "%02d:%02d", min, sec)
}
// 재생 버튼 클릭 시 호출되는 메서드
@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: .normal)
// 녹음 시간 타이머 시작
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: .normal)
btnPlay.isEnabled = true
initPlay()
}
}
// 녹음 시간 업데이트 메서드
@objc func updateRecordTime() {
lblRecordTime.text = convertNSTimeInterval2String(audioRecorder.currentTime)
}
}
MoviePlayer
: 비디오 재생 앱 만들기import UIKit
import AVKit // AVKit 프레임워크 임포트 (비디오 재생을 위한 프레임워크)
class ViewController: UIViewController {
// 뷰가 로드되었을 때 호출되는 메서드
override func viewDidLoad() {
super.viewDidLoad()
// 추가적인 설정이 필요한 경우 여기에 작성
}
// "내부 파일 재생" 버튼을 눌렀을 때 호출되는 메서드
@IBAction func btnPlayInternalMovie(_ sender: UIButton) {
// 앱 내에 번들에 포함된 "FastTyping.mp4" 파일의 경로를 찾습니다.
let filePath: String? = Bundle.main.path(forResource: "FastTyping", ofType: "mp4")
// 파일 경로가 유효하면 URL 객체를 생성
let url = NSURL(fileURLWithPath: filePath!)
// 비디오 재생 함수 호출 (비디오 URL 전달)
playVideo(url: url)
}
// "외부 파일 재생" 버튼을 눌렀을 때 호출되는 메서드
@IBAction func btnPlayerExternalMovie(_ sender: UIButton) {
// 외부 URL에서 비디오 파일을 가져옵니다 (Dropbox에서 제공하는 mp4 파일)
let url = NSURL(string: "https://dl.dropboxusercontent.com/s/e38auz050w2mvud/Fireworks.mp4")!
// 비디오 재생 함수 호출 (외부 URL 전달)
playVideo(url: url)
}
// 비디오 URL을 받아서 AVPlayer로 재생하는 메서드
private func playVideo(url: NSURL) {
// AVPlayerViewController 객체 생성 (비디오를 화면에 표시하는 컨트롤러)
let playerController = AVPlayerViewController()
// 주어진 URL로 AVPlayer 객체 생성
let player = AVPlayer(url: url as URL)
// AVPlayerViewController의 player 속성에 AVPlayer 설정
playerController.player = player
// AVPlayerViewController를 화면에 표시하고 비디오 재생 시작
self.present(playerController, animated: true) {
// 비디오 재생 시작
player.play()
}
}
}
CameraPhotoLibrary
: 카메라와 포토 라이브러리에서 미디어 가져오기import UIKit
import MobileCoreServices // 이미지 및 비디오 관련 미디어 타입을 관리하는 프레임워크
class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
@IBOutlet var imgView: UIImageView! // 이미지 뷰 (선택한 이미지나 비디오 썸네일을 표시할 곳)
let imagePicker: UIImagePickerController! = UIImagePickerController() // 이미지 피커 인스턴스 생성
var captureImage: UIImage! // 캡처된 이미지 저장
var videoURL: URL! // 캡처된 비디오 URL 저장
var flagImageSave = false // 이미지나 비디오 저장 여부를 결정하는 플래그
override func viewDidLoad() {
super.viewDidLoad()
// 뷰 로딩 후 추가적인 설정이 필요한 경우 여기서 할 수 있음
}
// 카메라에서 이미지를 촬영하는 버튼 액션
@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) // 비디오 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) // 알림 표시
}
}
DrawGraphics
: 코어 그래픽스로 화면에 그림 그리기import UIKit
class ViewController: UIViewController {
@IBOutlet var imgView: UIImageView! // 이미지 뷰, 도형을 그릴 곳
override func viewDidLoad() {
super.viewDidLoad()
// 뷰가 로드된 후 초기 설정이 필요한 경우 여기서 할 수 있음
}
// 직선을 그리는 버튼 액션
@IBAction func btnDrawLine(_ sender: UIButton) {
UIGraphicsBeginImageContext(imgView.frame.size) // 이미지 컨텍스트 시작
let context = UIGraphicsGetCurrentContext()! // 현재 그래픽 컨텍스트 얻기
// 빨간색 선을 그리기 위한 설정
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() // 선 그리기
// 삼각형 그리기
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()!
// 빨간색 선으로 사각형 그리기
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()!
// 타원 그리기 (빨간색 선)
context.setLineWidth(2.0)
context.setStrokeColor(UIColor.red.cgColor)
// 타원의 위치와 크기 설정
context.addEllipse(in: CGRect(x: 70, y: 50, width: 200, height: 100))
context.strokePath() // 타원 그리기
// 원 그리기 (녹색 선)
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()!
// 첫 번째 호 그리기 (빨간색 선)
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()!
// 빨간색 사각형 그리기 (채우기)
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() // 사각형 외곽선 그리기
// 파란색 원 그리기 (채우기)
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() // 원 외곽선 그리기
// 초록색 삼각형 그리기 (채우기)
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()
}
}
DrawGraphics
: 탭과 터치 사용해 스케치 앱 만들기import UIKit
class ViewController: UIViewController {
@IBOutlet var txtMessage: UILabel! // 터치 이벤트 상태 메시지를 표시할 레이블
@IBOutlet var txtTapCount: UILabel! // 터치의 탭 횟수를 표시할 레이블
@IBOutlet var txtTouchCount: UILabel! // 동시에 발생한 터치 개수를 표시할 레이블
override func viewDidLoad() {
super.viewDidLoad()
// 뷰가 로드된 후에 초기 설정을 진행할 수 있는 부분
}
// 사용자가 화면을 터치하기 시작했을 때 호출되는 메서드
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first! as UITouch // 첫 번째 터치 객체를 가져옵니다.
// 터치가 시작되었을 때 상태 메시지와 터치 정보를 레이블에 표시합니다.
txtMessage.text = "Touches Began"
txtTapCount.text = String(touch.tapCount) // 현재 터치의 탭 횟수를 표시합니다.
txtTouchCount.text = String(touches.count) // 동시에 발생한 터치 개수를 표시합니다.
}
// 사용자가 화면에서 손가락을 움직일 때마다 호출되는 메서드
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first! as UITouch // 첫 번째 터치 객체를 가져옵니다.
// 터치가 이동할 때 상태 메시지와 터치 정보를 레이블에 표시합니다.
txtMessage.text = "Touches Moved"
txtTapCount.text = String(touch.tapCount) // 현재 터치의 탭 횟수를 표시합니다.
txtTouchCount.text = String(touches.count) // 동시에 발생한 터치 개수를 표시합니다.
}
// 사용자가 화면에서 손가락을 떼었을 때 호출되는 메서드
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first! as UITouch // 첫 번째 터치 객체를 가져옵니다.
// 터치가 종료될 때 상태 메시지와 터치 정보를 레이블에 표시합니다.
txtMessage.text = "Touches Ended"
txtTapCount.text = String(touch.tapCount) // 현재 터치의 탭 횟수를 표시합니다.
txtTouchCount.text = String(touches.count) // 동시에 발생한 터치 개수를 표시합니다.
}
}
TapTouch
: 스와이프 제스처 사용하기PinchGesture
: 핀치 제스처 사용해 사진을 확대/축소하기추가로 Xcode
의 Simulator
에서 핀치 제스처는 Alt
키를 누른 상태에서 드래그하면 사용할 수 있어요.
BMI
앱 만들기지금까지의 내용과 실습 코드를 같이 활용해서 앱을 만들어볼게요.
앱을 통해 기능을 제공하는 경우 이미 앱스토어에 똑같은 기능을 제공하는 경우가 많아요.
어떤 점을 다르게 만들지 비교하기 위해서 기존에 있는 앱스토어를 확인할 수 있어요.
Xcode
에서 앱 개발할 때 기본이 되는 절차가 있어요.
UI
디자인Outlet
작성Action
작성Connections inspector
로 연결 확인Action
에서 소스코드 작성다음 내용과 같이 설정 후 진행해요.
다음과 같이 나타날 수 있도록 앱의 디자인을 해줘요.
입력 시 Height
와 Weight
는 숫자만 입력하게 하는 것이 오류를 방지할 수 있는 방법 중 하나에요.
다음과 같이 Text Field
의 속성 값 중 Keyboard Type
을 Decimal Pad
로 설정하면 숫자만 입력 가능한 키보드가 나타나게할 수 있어요.
Xcode
의 주요 UI
를 활용할 수 있는 소스코드를 확인했어요.오픈소스 출처 : Do it! 스위프트로 아이폰 앱 만들기 입문
송호정, 이범근 저, 이지스퍼블리싱, 2023년 01월 20일