페이지 컨트롤
콘텐츠의 여러 페이지나 항목 간을 탐색할 수 있도록 도와주는 UI
//
// ViewController.swift
// PageControl
//
// Created by Ho-Jeong Song on 2021/11/25.
//
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으로 설정 (4번째 이미지)
pageControl.currentPage = 3
// 페이지 인디케이터의 기본 색상을 파란색으로 설정
pageControl.pageIndicatorTintColor = UIColor.blue
// 현재 페이지 인디케이터의 색상을 회색으로 설정
pageControl.currentPageIndicatorTintColor = UIColor.gray
// 이미지 뷰에 첫 번째 이미지를 설정
imgView.image = UIImage(named: images[0])
}
// 페이지 컨트롤의 페이지가 변경될 때 호출되는 액션 메서드
@IBAction func pageChange(_ sender: UIPageControl) {
// 현재 페이지 인덱스에 해당하는 이미지를 이미지 뷰에 설정
imgView.image = UIImage(named: images[pageControl.currentPage])
}
}
탭바 컨트롤러
탭으로 여러 화면으로 이동할 수 있는 컨트롤러
네비게이션 컨트롤러
사용자가 여러 화면(뷰) 간에 탐색할 수 있도록 도와주며, 주로 스택 기반의 탐색 방식으로 작동
테이블 뷰
요소를 계층적으로 보여줌
가장 많이 사용하는 UI컨트롤러
1. UIViewController
설명: 기본적인 뷰 컨트롤러로, 앱의 화면을 구성하는 데 사용됩니다. 사용자 인터페이스 요소를 추가하고 이벤트를 처리하는 데 필수적입니다.
용도: 여러 UI 요소를 배치하고, 사용자와의 상호작용을 관리합니다. 서브클래스를 만들어 다양한 화면을 구현할 수 있습니다.
2. UITableViewController
설명: 테이블 뷰를 전문적으로 관리하는 컨트롤러입니다. 데이터의 리스트를 표시하고 사용자가 선택할 수 있는 항목을 제공합니다.
용도: 목록, 설정, 피드 등 다양한 데이터 집합을 표시할 때 사용됩니다. 스크롤 가능한 리스트를 쉽게 구현할 수 있습니다.
3. UINavigationController
설명: 여러 뷰 컨트롤러 간의 계층적 탐색을 관리하는 컨트롤러입니다. 스택 기반으로 작동하며, 화면 전환을 부드럽게 처리합니다.
용도: 사용자에게 "뒤로" 가기 기능을 제공하며, 여러 화면 간의 관계를 명확하게 만들어줍니다. 예를 들어, 목록에서 항목을 선택하면 상세 화면으로 이동할 때 사용됩니다.
음악을 재생하고 녹음하는 앱
//
// ViewController.swift
// Audio
//
// Created by BeomGeun Lee on 2021.
//
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() // 오디오 파일 선택
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 {
// 녹음 모드일 때, 문서 디렉토리에 저장될 파일 URL 설정
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 AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default)
try AVAudioSession.sharedInstance().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
}
// NSTimeInterval을 문자열로 변환
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() // 오디오 파일
동영상 재생
@IBAction func btnPlayInternalMovie(_ sender: UIButton) {
// 내부 파일 mp4
let filePath:String? = Bundle.main.path(forResource: "FastTyping", ofType: "mp4")
let url = NSURL(fileURLWithPath: filePath!)
playVideo(url: url)
}
@IBAction func btnPlayerExternalMovie(_ sender: UIButton) {
// 외부 파일 mp4
let url = NSURL(string: "https://dl.dropboxusercontent.com/s/e38auz050w2mvud/Fireworks.mp4")!
playVideo(url: url)
}
내부, 외부 비디오 재생 방식은 각각 장단점이 있음
사진 앱
화면에 그림 그리기
//
// ViewController.swift
// Sketch
//
// Created by Ho-Jeong Song on 2021/12/01.
//
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 // 흔들림이 감지되면 이미지 뷰를 지움
}
}
}
핀치 제스처
손가락으로 화면 확대/축소
앱 스토어에서 기존 bmi 앱 분석하기
BMI 계산기 - 체질량지수 계산기 & 무게 일기
이 무료 BMI 계산기를 사용하면 나이, 성별, 키, 체중을 입력하여 간편하게 자신의 BMI(체질량 지수)를 계산할 수 있습니다. WHO BMI 분류 방식에 근거하여 설계되었으며, 미터법 및 야드파운드법
apps.apple.com
iOS BMI 앱 기획서
1. 앱 개요
앱 이름: Simple BMI Calculator
목표: 사용자가 자신의 체질량지수(BMI)를 쉽게 계산하고, 건강 상태를 평가할 수 있도록 돕는 앱.
대상 사용자: 건강 및 피트니스에 관심이 있는 일반 대중.
2. 주요 기능
BMI 계산기:
사용자 입력: 키(센티미터)와 체중(킬로그램)을 입력받아 BMI 계산.
BMI 결과: 계산된 BMI 값을 표시하고, 해당 BMI 범주(저체중, 정상, 과체중, 비만)를 알려줌.
BMI 기록:
사용자가 과거의 BMI 기록을 저장하고, 그래프로 시각화하여 변화를 추적할 수 있도록 함.
건강 정보 제공:
BMI에 따른 건강 정보 및 팁 제공(예: 건강한 체중 범위, 식이요법, 운동 추천).
단위 변환:
미터 및 파운드와 같은 다른 단위로도 입력 가능하도록 변환 기능 추가.
3. UI/UX 디자인
홈 화면:
사용자 친화적인 인터페이스로 키와 체중 입력 필드, 계산 버튼을 배치.
결과를 시각적으로 강조하여 쉽게 이해할 수 있도록 디자인.
결과 화면:
계산된 BMI 값과 해당 범주를 시각적으로 표현.
건강 정보와 팁을 아이콘과 함께 제공하여 시각적 효과를 극대화.
기록 화면:
그래프와 리스트로 과거 BMI 기록을 쉽게 확인할 수 있도록 디자인.
날짜와 함께 BMI 값을 표시하여 사용자가 변화를 추적할 수 있도록 함.
4. 추가 기능
알림 기능:
사용자가 정기적으로 BMI를 입력하도록 알림 기능 추가.
소셜 공유:
사용자가 자신의 BMI 결과를 소셜 미디어에 공유할 수 있는 기능 추가.
다국어 지원:
다양한 언어로 지원하여 다양한 국가의 사용자들에게 접근 가능하도록 함.
5. 마케팅 전략
앱 스토어 최적화(ASO):
키워드 분석을 통해 "BMI", "체중 관리", "건강" 등의 키워드를 활용하여 앱 설명 작성.
소셜 미디어 마케팅:
Instagram, Facebook, Twitter 등을 통해 건강 관련 콘텐츠 및 사용자 후기 공유.
블로그 및 유튜브 협업:
건강 및 피트니스 관련 블로그 및 유튜버와 협업하여 앱을 소개하고 리뷰를 받음.
사용자 피드백:
초기 사용자 피드백을 통해 앱의 기능 개선 및 업데이트 계획 수립.
6. 기술 스택
개발 언어: Swift
프레임워크: UIKit, Core Data (기록 저장용), Charts (그래프 표시용)
디자인 툴: Sketch 또는 Figma
7. 일정 계획
1단계: 시장 조사 및 기획
2단계: 디자인 프로토타입 제작
3단계: 앱 개발
4단계: 베타 테스트 및 피드백 수집
5단계: 최종 출시
BMI 계산하는 다양한 코드들
let weight = 60.0
let height = 170.0
let bmi = weight / (height*height*0.0001) // kg/m*m
print(bmi)
---------------------------
let weight = 60.0 // 체중 (kg)
let height = 170.0 // 신장 (cm)
let bmi = weight / (height * height * 0.0001) // kg/m²로 변환
var body = "" // BMI 판정 결과 저장 변수
// BMI에 따른 판정
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: \(bmi), 판정: \(body)")
------------------------
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)")
--------------------------
import Foundation
func calcBMI(weight : Double, height : Double) -> String{
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 = "저체중"
}
return "BMI:\(shortenedBmi), 판정:\(body)"
}
print(calcBMI(weight:62.5, height: 172.3))
--------------------------
import Foundation
func calcBMI (weight : Double, height : Double) { //Void형
let bmi = weight / (height*height*0.0001) // kg/m*m
let shortenedBmi = String(format: "%.1f", bmi)
switch bmi {
case 0.0..<18.5:
print("BMI:\(shortenedBmi),판정:저체중")
case 18.5..<25.0 :
print("BMI:\(shortenedBmi),판정:정상")
case 25.0..<30.0 :
print("BMI:\(shortenedBmi),판정:1단계 비만")
case 30.0..<40.0 :
print("BMI:\(shortenedBmi),판정:2단계 비만")
default :
print("BMI:\(shortenedBmi),판정:3단계 비만")
}
}
calcBMI(weight:62.5, height: 172.3)
1. UI디자인
2. Outlet 설정
3. 함수 설정
4. outlet, action이 한번만 연결될 것인지 확인
5. action 소스코드 작성
이미지는 에셋 폴더에 넣음
아이폰, 아이패드의 해상도가 다르기 때문에 해상도 별 사진을 각각 준비해야함
버튼 디자인 변경
시뮬레이터에서 변경된 디자인이 보임
키보드 타입
텍스트 필드에서 숫자만 나오는 키보드로 변경
출처
- 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 핀치 제스처 사용해 사진을 확대/축소하기