✔︎ 오늘의 정리
- 첫 화면 실행 시 분기 전환
- View의 backgroundColor / Label의 textColor 한번에 설정하기
- unexpected nil window in... 오류 해결
- 코드로 backgroundColor에 투명도 주기
- textView editing 막기
- alertAction에 함수 넣기(handler)
- keyboard가 올라옴에 따라 view도 같이 올리고 내리기
Damagochi 과제 화면 구성

너무 헷갈리길래 그려봤다.
첫 화면 실행 시 분기 전환
//
// SceneDelegate.swift
// Damagochi
//
// Created by yeoni on 2023/08/05.
//
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
UserDefaults.standard.set(0, forKey: "chooseDamagochi")
// 첫 화면을 띄워주는 친구
guard let scene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: scene)
// 처음 실행시
let chooseDamagochi = UserDefaults.standard.integer(forKey: "chooseDamagochi") // 0
// 첫 화면 구성 코드
let sb = UIStoryboard(name: "Main", bundle: nil)
if chooseDamagochi == 0 {
guard let vc = sb.instantiateViewController(withIdentifier: "StartViewController") as? StartViewController else { return }
let nav = UINavigationController(rootViewController: vc)
window?.rootViewController = nav
} else {
guard let vc = sb.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController else { return }
window?.rootViewController = vc
}
window?.makeKeyAndVisible()
// 다마고치 고르고 실행 시
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
SceneDelegate에서 한다.
UserDefault 값을 이용하여 분기를 전환하며, UIWindow를 이용하여 실행한다.
View의 backgroundColor / Label의 textColor 한번에 설정하기
//
// AppDelegate.swift
// Damagochi
//
// Created by yeoni on 2023/08/05.
//
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UINavigationBar.appearance().tintColor = .fontColor
UINavigationBar.appearance().barTintColor = .fontColor
UILabel.appearance().textColor = .fontColor
UIView.appearance().backgroundColor = .bgColor
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
AppDelegate에서 일괄적으로 색깔을 설정해 주면 앱 실행 시 지정해준 것의 색깔이 일괄적으로 변경된다.
아무리 눌러도 Description이 안 올라갈 때
각 뷰컨트롤러끼리 객체를 주고 받는데, 변경하는 값이 실제 값이 아니라서 그랬다.
무슨 말이냐하면, 뷰 컨트롤러에서 해당 데이터를 변수로 만들어 담는다면 그 변수는 해당 데이터 자체가 아니라 그의 복사본을 담게 된다.
그 복사본을 이리저리 바꾸어도, 해당 데이터는 변경되지 않는다는 말이다.
따라서, 단순히 데이터를 출력하는 것이 아니라 실제 데이터를 변경해 주고 그 데이터를 이용하여 무언가 해야 할 때는 외부 모듈에 변수를 만들어 그 변수를 옮기기 전 뷰 컨트롤러에서 저장하고, 이후 다른 뷰 컨트롤러에서 그 변수의 데이터값을 가져오는 것이 좋다.
이때, 해당 데이터를 뷰 컨트롤러 안에서 직접적으로 값을 변경하는 게 좋을까 아니면 그 값을 복사한 변수를 만든 후에 그 변수를 이용하여 값을 변경하고, 뷰가 꺼질 때 데이터에 바뀐 값을 저장하는 게 좋을까?
개인적인 생각으로는 둘이 비슷할 거 같은데, 왠지 후자가 더 좋지 않을까 싶다. 모든 뷰 컨트롤러에서 직접적으로 데이터값을 변경하게 된다면 오류가 났을 때 어느 곳에서 오류가 났는지 찾기 힘들 것이다. 뭔가 디버깅에서뿐만 아니라 데이터를 저장하고 가져오는 데 시간이 조금 더 걸리게 되니까 후자가 낫지 않을까 싶다.
오류 해결
[Touch] unexpected nil window in __sendSystemGestureLatentClientUpdate, _windowServerHitTestWindow: <UIRemoteKeyboardWindow: 0x1458e3000; frame = (0 0; 393 852); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x600000306b20>>, touch:<UITouch: 0x14840a1d0> phase: Stationary tap count: 1 force: 0.000 window: (null) responder: (null)
진짜 해결하고 싶어 죽는 줄 알았다
문제 상황을 적어 보자면, 메인 화면의 TextField를 클릭하여 입력하려고 하면 빈 화면이 뜨면서 앱이 동작하지 않았다.
말 그대로 unexpected nil window... ... ^_^
검색해 보니 iOS8 업데이트가 되면서 생긴 오류라며 AppDelegate에 무슨 코드를 추가해 주기만 하면 된다! 였는데 내 상황에는 전혀 맞지 않았다.
그야 당근 해당 코드는 이제 AppDelegate가 아닌 SceneDelegate에서 담당할 것 같았기 때문이다. (사유: window...)
멘토님께 실쩍 여쭤보기는 싫구 아니!! 분명 어디서 문제가 생긴 거 같은데 도통 보이지는 않고 검색해 봐도 모르겠어서 일단 AppDelegate쪽과 SceneDelegate 코드를 작성한 걸 차례차례 없애봤다가 이건 문제없군.. 하구 다시 살리고 또 다른 코드를 지워보고 살려보고 반복했다.
그러다가!!!!!!!!
결국 찾았다.
문제가 됐던 코드는 AppDelegate에 있었다.
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UINavigationBar.appearance().tintColor = .fontColor
UINavigationBar.appearance().barTintColor = .fontColor
UILabel.appearance().textColor = .fontColor
UIView.appearance().backgroundColor = .bgColor
return true
}
... ...
}
뭐가 문제인지 몰랐는데, 문제는 UINavigationBar였다. 해당 Appearance를 주석처리하니 잘 돌아가더라.
날린 시간이 너무 허망했지만.............
그래도 나중에 또 같은 오류가 나면 잘 해결할 수 있을 것 같다.
현재 AppDelegate 안의 코드는 이렇다.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UILabel.appearance().textColor = .fontColor
return true
}
UIView 코드까지 삭제해 준 이유는 DetailView를 팝업 형식으로 띄울 때 왜? 배경이 투명하게 보이지 않지?!?!?!?!? 했는데
알고보니 내가 설정해 준 친구 때문이었다.
AppDelegate에서 전체 설정을 건드리는 건 잘해야 하는구나...... 하구 새삼 느꼈다.
이거 땜에 삽질한 시간이 또........
아무튼간 그래서
코드로 backgroundColor에 투명도 주기
해당뷰이름.backgroundColor = .black.withAlphaComponent(0.4)
이런 식으로 하면 댄다.


전체 View의 backgroundColor를 .black으로만 줬을 때와
withAlphaComponent method를 이용했을 때이다.
bgView.backgroundColor = .black // 첫 번째 사진
bgView.backgroundColor = .black.withAlphaComponent(0.4) // 두 번째 사진
textView Edit 불가능하게 하는 법
textView.isEditable = false
alertAction에 함수 넣기(handler)
UIAlertAction의 생성자를 보면,

이런 식으로 되어 있다.
title과 style parameter는 필수적으로 작성해 주어야 하고, handler는 기본적으로 nil로 되어 있어 필요한 경우에만 넣어주면 된다.
handler를 잘 쓰지 않아 몰랐는데, alertAction을 눌렀을 때 특정 함수가 실행되도록 하려면 handler에 클로저를 넣으면 된다.
예시를 가져오겠다...

func deleteData() {
let alert = UIAlertController(title: "초기화하시겠습니까?", message: "모든 데이터가 삭제됩니다!", preferredStyle: .alert)
let cancel = UIAlertAction(title: "취소", style: .default)
let accept = UIAlertAction(title: "확인", style: .default) { _ in
user.myDamagochi = Damagochi(type: .setting, eatBab: 0, drinkWater: 0)
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
let SceneDelegate = windowScene?.delegate as? SceneDelegate
let sb = UIStoryboard(name: "Main", bundle: nil)
guard let vc = sb.instantiateViewController(withIdentifier: "StartViewController") as? StartViewController else { return }
let nav = UINavigationController(rootViewController: vc)
UserDefaults.standard.set(1, forKey: "currentStatus")
SceneDelegate?.window?.rootViewController = nav
SceneDelegate?.window?.makeKeyAndVisible()
}
alert.addAction(cancel)
alert.addAction(accept)
present(alert, animated: true)
}
확인 버튼을 누르면 데이터를 삭제하는 코드이다.
적용한 alert 방식은 완전히 같지만, 클로저 내의 코드만 다른 예시를 하나 더 가져오자면...

func changeDamagochi() {
let alert = UIAlertController(title: "다마고치를 변경하시겠어용?", message: "밥과 물을 준 횟수는 그대로 유지돼요!", preferredStyle: .alert)
let cancel = UIAlertAction(title: "취소", style: .default)
let accept = UIAlertAction(title: "확인", style: .default) { _ in
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
let SceneDelegate = windowScene?.delegate as? SceneDelegate
let sb = UIStoryboard(name: "Main", bundle: nil)
guard let vc = sb.instantiateViewController(withIdentifier: "StartViewController") as? StartViewController else { return }
let nav = UINavigationController(rootViewController: vc)
UserDefaults.standard.set(1, forKey: "currentStatus")
user.status = .changeDamagochi
SceneDelegate?.window?.rootViewController = nav
SceneDelegate?.window?.makeKeyAndVisible()
}
alert.addAction(cancel)
alert.addAction(accept)
present(alert, animated: true)
}
다마고치를 바꾸는 코드이다.
키보드 올리고 내리기
class MainViewController: UIViewController {
... ...
@IBOutlet weak var babTextField: UITextField!
@IBOutlet weak var waterTextField: UITextField!
... ...
override func viewDidLoad() {
super.viewDidLoad()
... ...
babTextField.delegate = self
waterTextField.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
... ...
}
// keyboard 올리고 내리고에 따라서 view도 같이 올리고 내리기
extension MainViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
@objc
func keyboardWillShow(_ sender: Notification) {
self.view.frame.origin.y = -150 // Move view 150 points upward
}
@objc
func keyboardWillHide(_ sender: Notification) {
self.view.frame.origin.y = 0 // Move view to original position
}
}
해당 뷰컨트롤러에 UITextFieldDelegate 추가 후, 원하는 TextField에 delegate 속성을 넣어준다.
이후, 위 방식처럼 넣어주면 댄다.. ^_^ 6
코드는 아래 블로그를 참조했다.
https://zeddios.tistory.com/127
iOS ) 키보드에 의해 TextField가 가려지는 현상 해결 (Swift)
안녕하세요 :) Zedd입니다. 오늘은 TextField에 대해서 배워볼건데요 :)TextField를 쓰다보면 반드시 접하는 이슈..!바로 이 문제죠.바로 키보드에 의해 TextField가 가려지는 현상입니다.간단하게 오늘 할
zeddios.tistory.com
지금 발등에 불떨어지다못해 걍 허벅지까지 탔다,,
이래도 되는건가??? 나이렇게해도되나???? 이렇게???? 되나????? 하면서 미친듯이 코딩하는 중
집에 일이 있어서 지금 ㅠ,ㅠ 엊그제는 UI만 짜놓고 오늘 기능구현 조진다!! 햇는데
막상 조져지는건 나엿음...

하하 지금은 기능 구현 다했다 .ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ.ᐟ
힘들어죽겟다.............
내일 할 일
1. 이름 관련 버그 수정
2. nameLabel inset 넣기
3. 이스터에그 넣기...........
주말 동안 과제 하면서 깨달은 부족한 점
- 네비게이션 바를 이용한 화면 전환에 서툴다
가만 보면 이......... 이거 어떻게 했더라??? 하고 있다...... nav 변수를 선언해야 할 때와 하지 않아도 괜찮을 때를 잘 모르겠다... 이건 내 스스로 좀 더 공부하고 복습해야 알 것 같다.............. 약간 감으로만 알고 있고 어?? 이때 되네?? 하고 걍 넘기고 어?? 이거 왜 안되지?? 이걸로해보까 하고 이걸로 해서 되면 안일하게 넘어가다 보니까 ㄹㅇ 모르는 거 같다...... - App의 생명 주기와 Scene의 LifeCycle을 조금 더 공부하면 좋을 거 같다
UserDefault 저장하면서 생각한 건데 뭔가 알고 있기는 하지만 명확히 어느 때 이 함수가 실행된다고 알고 있는 건 아니어서 자꾸 print(#function) 하게 된다 print문 없으면 디버깅 어케 하지? 아무튼...
아......
근데 요즘 너무 재밋다.......
매일매일 새로운 걸 왕창!!!! 배우고 그걸 다 기억하지는 못하지만 어제보다 훨씬 나아지고 잇는 나를 보게 대면 너무 즐겁다...........
앞으로도재밋엇음좋겠다...
'TIL' 카테고리의 다른 글
[SeSAC] August 10, 2023 (0) | 2023.08.11 |
---|---|
[SeSAC] August 7, 2023 (0) | 2023.08.07 |
[SeSAC] August 3, 2023 (2) | 2023.08.04 |
[SeSAC] August 2, 2023 (0) | 2023.08.02 |
[SeSAC] August 1, 2023 (0) | 2023.08.01 |