알림 권한 설정
foreground에서 알림 여부
// 알림 권한 설정
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(
options: [.badge, .alert, .sound]) { success, error in
print(success, error)
}
return true
}
// foreground에서 알림 여부 설정
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler( [.sound, .badge, .banner, .list] )
}
}
배지, 알림 센터의 알림 개수 조절
func sceneDidBecomeActive(_ scene: UIScene) {
// 배지 개수 조절
UIApplication.shared.applicationIconBadgeNumber = 10
// 스택에 떠있던 모든 알림 제거
UIUserNOtificationCenter.current().removeAllDeliveredNotifications()
// 대기중이었던 알림 제거
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
// remove로 사용해서 특정 알림만 제거하는 것도 가능
}
// 1. 컨텐츠 설정
let content = UNMutableNotificationContent()
content.title = "알림 타이틀입니다"
content.body = "알림 바디입니다. 다마고치에게 물을 주세요"
content.badge = 99 // 초기 배지 개수
// 2. 언제 (trigger)
// 2 - 1. time interval
let trigger = UNTimeINtervalNotificationTrigger(
timeInterval = 1,
repeats: false // repeats이 true라면 최소 interval = 60
}
// 2 - 2. calendar
var component = DateComponents()
component.minute = 5
component.hour = 10
let trigger = UNCalendarNotificationTrigger(
dateMatching: component,
repeats: false
}
// 3. Notification
let request = UNNotificationRequest(
identifier: "\(Date)" // 하나의 identifier당 하루에 64개 제한
content: content
trigger: trigger
}
UNUserNotificationCenter.current().add(request) { error in
print(error)
}
@IBDesignable
class SeSACButton: UIButton {
@IBInspectable
var jack: CGFloat {
get {
return layer.cornerRadius
}
set {
layer.cornerRadius = newValue
}
}
@IBInspectable
var borderWidth: CGFloat {
get { return layer.borderwidth }
set { layer.borderwidth = newValue }
}
@IBInspectable
var borderColor: UIColor {
get { return UIColor(cgColor: layer.borderColor!) }
set { layer.borderColor = newValue.cgColor }
}
}
lazy var
+ func
사용let/var
+ static func
사용view.addSubview()
레이아웃
a. frame 기반 -> 한계 (디바이스의 크기가 다양해짐)
b. AutoResizingMask, AutoLayout -> 스토리보드
c. NSLayoutConstraint
translatesAutoresizingMaskIntoConstraints = false
isActive
addConstraints
d. NSLayoutAnchor
e. SnapKit
1. import
import CoreLocation
import MapKit
2. 인스턴스 생성
let locationManager = CLLocationManager()
let mapView = MKMapView()
3. 프로토콜 연결
locationManager.delegate = self
mapView.delegate = self
4. 내장함수
extension LocationViewController: CLLocationManagerDelegate {
// 사용자의 위치를 성공적으로 가져온 경우
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// 날씨 API를 호출하거나, 지도의 annotation을 추가하기
// 사용자의 위치에 지도 anotation 추가하고, 화면 포커스 지정
if let coordinate = location.last?.coordinate {
setRegionAndAnnotation(center: coordinate) // 보여지는 범위 조정
}
locationManager.stopUpdatingLocation() // 이제 stop
}
// 사용자의 위치를 가져오는 데 실패한 경우
// (권한 거부, gps 박살, ...)
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
}
// 사용자의 권한 상태가 바뀐 경우
// (거부 -> 허용, notDetermined -> 허용/거부 등등)
// - iOS 14 이상
func locationManagerDidChangeAuthorization(_manager: CLLocationManager) {
checkDeviceLocationAuthorization()
}
// - iOS 14 미만
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
}
}
extension LocationViewController: MKMapViewDelegate {
// 지도를 움직였다가 멈추면 호출
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
}
// 뭐 선택하면 호출
func mapView(_ mapView: MKMapView, didSelec annotation: MKAnnotation) {
}
}
5. 커스텀 함수
// 원하는 annotation을 설정
func setAnnotation(type: Int) {
// mapView.removeAnnotation
// mapView.addAnnotation
// mapViwe.removeAnnotations(mapView.annotations) // 다지워
}
// 화면에 보여질 위치(범위) 설정 + annotation 추가
func setRegionAndAnnotation(center: CLLocationCoordinate2D) {
// 위치
let region = MKCoordinateRegion(
center: center,
latitudinalMeters: 100,
longitudinalMeters: 100
)
mapView.setRegion(region, animated: true)
// annotation
let annotation = MKPointAnnotation() // 기본 모양
annotation.title = "여긴 어디입니다"
annotation.coordinate = center
mapView.addAnnotation(annotation)
}
// 위치 권한을 허용해달라는 alert 실행 -> 설정 창으로 바로 이동
func showLocationSettingAlert() {
let alert = UIAlertController(
title: "위치 정보 이용",
message: "위치 서비스를 이용할 수 없습니다. 기기의 '설정>개인정보 보호'에서 위치 서비스를 켜줘",
preferredStyle: .alert)
)
let goSetting = UIAlertAction(title: "설정으로 이동", style: .default) { _ in
if let appSetting = URL(string: UIApplication.openSettingURLString) {
UIApplication.shared.open(appSetting)
}
}
// 나머지는 alert 코드와 동일
}
// 위치 서비스 권한 체크 -> DispatchQueue.global
func checkDeviceLocationAuthorization() {
DispatchQueue.global().async {
if CLLocationManager.locationServicesEnabled() { // 기기 자체의 위치 서비스 권한
let authorization: CLAuthorizationStatus // enum (0 ~ 5)
// 사용자의 위치 권한
if #available(iOS 14.0, *) {
authorization = self.locationManager.authorizationStatus
}
else {
authorization = CLLocationManager.authorizationStatus()
}
// 타 작업은 다시 DipatchQueue.main
DispatchQueue.main.async {
self.checkCurrentLocationAuthorization(status: authorization)
}
}
else {
print("위치 서비스가 꺼져 있기 때문에 위치 권한 요청을 할 수 없습니다")
}
}
}
// 현재 권한에 따른 작업 실행
func checkCurrentLocationAuthorization(status: ClAuthorizationStatus) {
switch status {
case .notDetermined:
// 정확도 설정
locationManager.desiredAccuracy = kCLLLocationAccuracyBest
// 권한 선택 alert (info.plist 필수)
locationManager.requestWhenInUseAuthorization()
case .restricted:
case .denied:
// 위치 권한 설정 유도
showLocationSettingAlert()
case .authorizedAlways:
case .authorizedWhenInUse:
// didUpdateLocation 메서드 실행
locationManager.startUpdatingLocation()
case .authorized:
@unknown default: // "위치 권한 종류가 추후에 더 생길 가능성 대비"
}
6. viewDidLoad
override func viewDidLoad) {
super.viewDidLoad()
checkDeviceLocationAuthorization()
}
let picker = UIImagePickerController()
let fontPicker = UIFontPickerViewController() // 폰트 피커
let colorPicker = UIColorPickerViewController() // 컬러 피커
// 소스 타입에 접근 가능한지 확인 (.photoLibrary, .camera)
guard let UIImagePickerController.isSourceTypeAvailable(.photoLibrary) else {
print("갤러리 사용 불가. 사용자에게 얼럿 메세지 띄워주기")
return
}
picker.delegate = self
picker.sourceType = .photoLibrary
picker.allowsEditing = true
// allowsEditing : 편집할 수 있는 창
// => didFinish에서 originalImage -> editedImage로 변경
present(picker, animated: true)
extension TextViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// 취소 시 실행
func imagePickerControllerDidCancel(_ picker: UIImagePicerController) {
dismiss(animated: true)
}
// (사진 선택 or 카메라 촬영) 직후
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
// info : 딕셔너리 타입
if let image = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
// 편집한 이미지 : editedImage
// 원본 이미지 : originalImage
self.photoImageView.image = image
dismiss(animated: true)
}
}
}
func configureBorder<T, UIView)(view: T) {
view.layer.borderColor = UIColor.black.cgColor
view.layer.borderWidth = 1
}
func sum<T: AdditiveArithmetic>(a: T, b: T) -> T {
return a + b
}
enum TransitionStyle {
case present // 네비게이션 없이 present
case presentNavigation // 네비게이션 달아서 present
case presentFullNavigation // 네비게이션 달고 fullscreen으로 present
case push // 푸쉬이
}
func transition<T: UIViewController>(viewController: T.Type, storyboard: String, style: TransitionStyle) {
// 1. 스토리보드가 살아있을 때
let sb = UIStoryboard(name: storyboard, bundle: nil)
guard let vc = sb.instantiateViewController(withIdentifier: String(describing: viewController))
as? T else { return }
// 2. 스토리보드가 죽었을 때
let vc = T()
// let vc = viewController.init() 동일하게 실행
// 화면 전환 방식
switch style {
case .present:
present(vc, animated: true)
case .presentNavigation:
let nav = UINavigationController(rootViewController: vc)
present(nav, animated: true)
case .presentFullNavigation:
let nav = UINavigationController(rootViewController: vc)
nav.modalPresentationStyle = .fullScreen
present(nav, animated: true)
case .push:
navigationController?.pushViewController(vc, animated: true)
}
}
transition(viewController: GenericViewController.self, storyboard: "Main", style: .presentFullNavigation)
<T: UIViewController>
(viewController: T.Type, storyboard: String, ...)
T.Type -> T 자체의(?) 타입!!
T -> 지금 들어온 그 특정 하나의 클래스
guard let vc = ... as? T else { return }
let vc = T()
let vc = viewController.init()
main.storyboard
파일 제거 (Move To Trash)info.plist
-> storyboard Name Delete
build setting
-> Erase Main
// SceneDelegate.swift
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions:UIScene.ConnectionOptions) {
guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: scene)
let vc = GenericViewController()
window?.rootViewController = vc
window?.makeKeyAndVisible()
}