본문 바로가기
프로그래밍/iOS

[iOS] Swift 공부 - 15

by Cian 2024. 3. 28.

▶ View

iOS 앱에서 UI를 구성하는 기본적인 단위로, 사용자가 화면에서 보는 UI 요소들을 나타내며, 역할은 콘텐츠를 담아 이를 스크린 상에 표시하고, 사용자의 입력에 반응하는 객체이다.

 

-뷰 계층(View Hierarchy): 뷰는 계층적 구조를 가지며, 하나의 뷰는 여러 개의 하위 뷰를 포함할 수 있고, 이들은 트리 구조로 관리된다. 

-윈도우와 뷰의 관계: iOS에서 화면에 앱의 콘텐츠를 나타내기 위해 윈도우와 뷰를 사용한다. 윈도우는  자체로 콘텐츠를 표현할  없으며, 애플리케이션의 뷰를 통해 사용자에게 콘텐츠를 보여준다.

 

 

 UIWindow 클래스

UIWindow 클래스는 iOS 애플리케이션의 사용자 인터페이스의 배경을 형성하고, 뷰에 이벤트를 전달하는 객체이다. UIKit 프레임워크의 일부로, 앱의 시각적 콘텐츠를 담는 컨테이너 역할을 하며, 사용자와의 상호작용을 관리한다. 

 

- UIWindow의 기본 역할

   > 이벤트 처리와 전달: UIWindow는 앱의 사용자 인터페이스의 배경이자, 뷰로 이벤트를 전달하는 객체이다. 사용자의 터치, 키보드 입력 등의 이벤트를 받아서 적절한 뷰로 전달하는 중요한 역할을 한다.

   >콘텐츠 표시: 애플리케이션의 메인 윈도우로서, 앱의 콘텐츠를 화면에 표시한다. 대부분의 앱은 하나의 메인 윈도우를 가지지만, 필요에 따라 추가 윈도우를 생성하여 사용할 수도 있다.

 

- UIWindow 사용 시나리오

   > 메인 윈도우 제공: 앱의 메인 콘텐츠를 표시하는 데 사용된다. Xcode 프로젝트를 생성할 때, 기본적으로 메인 윈도우를 제공한다.

   > 추가 윈도우 생성: 특정 상황에서 추가적인 콘텐츠를 표시하기 위해 추가 윈도우를 생성할 수 있다. 예를 들어, 외부 디스플레이에 콘텐츠를 표시할 때 추가 윈도우가 사용될 수 있다.

 

- UIWindow와 관련된 주요 개념

   > SceneDelegate: iOS 13 이상에서는 UIWindow 관리하기 위해 SceneDelegate 도입되었다. SceneDelegate 앱의 여러 인스턴스() 관리하며,  씬에 대한 윈도우와 생명주기 이벤트를 처리한다.

 

 

 간단한 전광판 스토리보드

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var label: UILabel!
    @IBAction func displayText(_ sender: Any) {
        label.text = textField.text
        UIView.animate(withDuration: 10, delay: 0, animations: {
            self.label.center.x += self.view.bounds.width
        })
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

 

 

▶ 오토레이아웃을 구현하는 4가지 방법의 장단점

Storyboard - 직관적이고 사용하기 쉽다. 뷰의 배치를 시각적으로 확인하며 제약 조건을 설정할 수 있다. - 복잡한 레이아웃을 구성하는 데 한계가 있다. 여러 사람이 함께 작업할 때 충돌이 발생하기 쉽다.
코드 - 제약조건을 세밀하게 조절할 수 있다. 동적인 레이아웃을 구성할 수 있으며, 버전 관리가 쉽다. - 코드가 복잡해질 수 있다. 레이아웃의 변경 사항을 확인하려면 빌드가 필요하다.
라이브러리 - 코드가 간결하고 이해하기 쉽다. 복잡한 레이아웃을 쉽게 구성할 수 있다. - 외부 라이브러리에 의존하게 된다. 라이브러리의 업데이트나 호환성 문제가 발생할 수 있다.
SwiftUI - 선언적 구문을 사용하여 코드가 간결하고 이해하기 쉽다. 자동으로 다양한 화면 크기와 장치에 맞게 레이아웃을 조절한다. - iOS 13 이상에서만 사용할 수 있다. 아직까지는 UIKit에 비해 제공하는 기능이 제한적이다.

 

 

 오토레이아웃 기본 개념과 용어

- Constraints (제약 조건): 뷰의 크기와 위치를 정의하는 규칙이다. 예를 들어, 한 뷰가 다른 뷰에 상대적으로 얼마나 떨어져 있는지, 뷰의 너비나 높이는 얼마나 되어야 하는지 등을 지정할 수 있다.

- Priority (우선 순위): 여러 제약 조건이 충돌할 때 어떤 제약 조건을 우선적으로 적용할지 결정한다. 우선 순위가 높은 제약 조건이 우선적으로 적용된다.

- Content Hugging & Compression Resistance: 뷰가 컨텐츠를 얼마나 꼭 끌어안을 것인지(컨텐츠 허깅), 또는 컨텐츠를 얼마나 압축에 저항할 것인지(압축 저항)를 결정하는 속성이다. 이를 통해 뷰의 크기가 컨텐츠에 따라 어떻게 변할지 제어할 수 있다.

 

 

 오토 레이아웃을 사용하는 이유

- 유연성: 다양한 화면 크기와 방향에 대응할 수 있어, 앱의 유연성이 증가한다.

- 유지 보수: 화면 크기가 변경되거나 새로운 디바이스가 출시되어도 기존의 제약 조건을 기반으로 쉽게 조정할 수 있어, 유지 보수가 용이하다.

- 시각적 일관성: 다양한 화면에서도 일관된 레이아웃과 디자인을 유지할 수 있다. 

 

 

 옵셔널에 대한, 조금은 더 구체적인 설명

옵셔널의 고급 사용법을 이해하고 적절히 활용하면, Swift 프로그래밍에서 더욱 안전하고 효율적인 코드를 작성할  있다. 이러한 기법들은 특히 복잡한 데이터 구조를 다루거나, 불확실한 값에 대한 처리가 필요할   진가를 발휘한다.

 

-옵셔널 체이닝과 바인딩을 활용한 복잡한 데이터 구조 접근

   > 옵셔널 체이닝(Optional Chaining): 옵셔널 체이닝을 사용하여 복잡한 데이터 구조 내부의 옵셔널 값을 안전하게 접근할 수 있다. 예를 들어, 여러 단계의 옵셔널 프로퍼티를 가진 객체에서 특정 값을 추출하려 할 때, 중간 어느 단계에서라도 nil이 발생하면 즉시 nil을 반환하고 더 이상의 접근을 중단한다.

// 옵셔널 체이닝을 사용하여 복잡한 데이터 구조 내부의 옵셔널 값을 안전하게 접근하는 방법
class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let cian = Person()
if let roomCount = cian.residence?.numberOfRooms {
    print("Cian의 집에는 \(roomCount)개의 방이 있다.")
} else {
    print("Cian의 집에 대한 정보를 찾을 수 없다.")
}

 

 > 옵셔널 바인딩(Optional Binding): if let 또는 guard let을 사용하여 옵셔널 바인딩을 통해 옵셔널 값을 안전하게 추출하고, 이를 통해 복잡한 조건부 로직을 구현할 수 있다. 이 방법은 특히 API 호출 결과나 사용자 입력과 같이 불확실한 값을 다룰 때 유용하다.

 

- 옵셔널 맵(Map)과 플랫맵(FlatMap)을 이용한 함수적 접근

   > 옵셔널 맵(Map): 옵셔널에 저장된 값(있을 경우)에 대해 함수를 적용하고 싶을 때 사용한다. 이는 옵셔널이 nil이 아닐 때만 해당 함수를 적용하고, 결과를 다시 옵셔널로 감싸서 반환한다.

   > 옵셔널 플랫맵(FlatMap): 옵셔널 맵과 유사하지만, 반환된 결과가 옵셔널의 옵셔널(예: Optional<Optional<Type>>)이 되는 것을 방지하고, 단일 옵셔널로 평탄화한다. 이는 중첩된 옵셔널 구조를 간결하게 처리할 수 있게 한다.

// 옵셔널에 저장된 값에 함수를 적용하거나 중첩된 옵셔널 구조를 평탄화하는 방법
let possibleString: String? = "An optional string."
let possibleLength: Int? = possibleString?.map { $0.count }

print(possibleLength) // Optional(18)

let nestedOptional: Int?? = Optional(Optional(42))
let flattened: Int? = nestedOptional.flatMap { $0 }

print(flattened) // Optional(42)

 

- 암시적 추출 옵셔널(Implicitly Unwrapped Optionals)의 사용

   > 암시적 추출 옵셔널: 초기화 이후에는 값이 항상 존재한다고 확신할 수 있는 경우에 사용한다. 변수 또는 상수 선언 시 느낌표(!)를 사용하여 선언하며, 이후에는 옵셔널이 아닌 것처럼 접근할 수 있지만, 사용에 주의가 필요하다. 초기화 전이나 값이 없는 상태에서 접근하면 런타임 오류가 발생할 수 있다. 

// 초기화 이후 값이 항상 존재한다고 확신할 때 사용하는 방법
let assumedString: String! = "암시적으로 추출된 옵셔널 문자열"
let implicitString: String = assumedString // 암시적 추출 옵셔널은 옵셔널이 아닌 것처럼 사용할 수 있다.

print(implicitString) // "암시적으로 추출된 옵셔널 문자열"

 

 

 Nil Coalescing Operator ("??")

Swift에서 ?? 연산자는 옵셔널 값이 nil일 경우 기본값을 제공하는 데 사용된다. 이를 통해 코드의 안정성을 높이고, 옵셔널 값의 처리를 간결하게 할 수 있다. ?? 연산자는 기본값을 제공함으로써 옵셔널 값의 처리를 간결하고 안전하게 만들어 준다. 특히, 사용자 입력이나 네트워크 통신으로부터 받은 데이터 처리 시 유용하게 사용될 수 있다. ?? 연산자는 옵셔널 값이 nil이 아닐 경우 그 값을 사용하고, nil일 경우 지정된 기본값을 사용한다.

let optionalValue: String? = nil
let defaultValue = "기본 문자열"
let result = optionalValue ?? defaultValue
print(result) // "기본 문자열" 출력

 

위 예시에서 optionalValue nil이므로, ?? 연산자 뒤에 오는 defaultValue result에 할당된다. 또한 ?? 연산자는 옵셔널 체이닝과 함께 사용되어, 복잡한 옵셔널 표현식에서도 기본값을 쉽게 제공할 수 있다.

class Person {
    var job: Job?
}

class Job {
    var title: String?
}

let person = Person()
let jobTitle = person.job?.title ?? "무직"
print(jobTitle) // "무직" 출력

 

위 코드에서 person.job?.title nil을 반환한다. 따라서 ?? 연산자 뒤에 오는 "무직" jobTitle에 할당된다.

 

 

 animate(with Duration:animations:completion:) 함수

UIView 애니메이션을 쉽게 구현할  있게 해주는 강력한 도구이다.  함수를 사용하면 시간 간격을 설정하고, 애니메이션을 정의하며, 애니메이션이 완료된  실행할 코드 블록을 지정할  있다.

 

- 함수 정의: animate(withDuration: TimeInterval, animations: () -> Void, completion: ((Bool) -> Void)?)

   > withDuration: 애니메이션이 실행될 시간(초 단위)

   > animations: 애니메이션으로 변경될 속성들을 정의하는 클로저

   > completion: 애니메이션이 완료된  실행될 클로저이다. 애니메이션이 성공적으로 완료되었는지 여부를 나타내는 Bool 값을 인자로 받는다.

UIView.animate(withDuration: 0.5, animations: {
    // 애니메이션으로 변경할 속성들
    someView.alpha = 0.5
    someView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
}, completion: { finished in
    // 애니메이션이 완료된 후 실행할 코드
    if finished {
        print("애니메이션 완료!")
    }
})

 

- 주의사항

애니메이션 동안 사용자 상호작용은 일시적으로 비활성화된다.

애니메이션 가능한 속성에는 frame, bounds, center, transform, alpha, backgroundColor 등이 있다.

 

 

 메서드 (Method)

특정 타입에 속한 함수이다. 클래스, 구조체, 열거형 등의 인스턴스에서 호출할 수 있는 함수를 의미한다. 메서드는 해당 타입의 인스턴스 내부 데이터에 접근하고 수정할 수 있는 기능을 제공한다.

 

-메서드의 기본 구조

Swift의 메서드는 크게 인스턴스 메서드와 타입 메서드로 나뉜다.

   > 인스턴스 메서드: 특정 타입의 인스턴스에 속한 함수로, 해당 인스턴스의 속성에 접근하거나 인스턴스의 기능을 수행한다.

   > 타입 메서드: 타입 자체에 호출되는 메서드로, static 또는 class 키워드를 사용하여 정의한다. 클래스의 경우 class 키워드를 사용하면 서브클래스에서 오버라이드할 수 있다.

 

-메서드 디스패치

메서드 디스패치는 Swift에서 함수와 메서드 호출을 처리하는 방식입니다. 이는 컴파일 타임과 런타임 성능에 영향을 미친다. Swift 주로  가지 메서드 디스패치 방식을 사용한다. 직접 디스패치, 테이블 디스패치, 메시지 디스패치. 이러한 방식은 타입의 성능과 유연성 사이의 균형을 결정한다.

 

Swift 제네릭을 사용한 메서드는 코드의 재사용성과 유연성을 높여준다. 특히, where 절을 사용하여 제네릭 타입에 대한 요구사항을 더욱 세밀하게 지정할  있다.

func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
    where T.Element: Equatable, T.Element == U.Element {
        for lhsItem in lhs {
            for rhsItem in rhs {
                if lhsItem == rhsItem {
                    return true
                }
            }
        }
        return false
}

 

 예제에서 anyCommonElements 함수는  시퀀스(lhs rhs) 사이에 공통 요소가 있는지 확인한다. 여기서 T U Sequence 프로토콜을 준수해야 하며,  시퀀스의 요소 타입은 Equatable 프로토콜을 준수해야 하고 서로 같아야 한다.

'프로그래밍 > iOS' 카테고리의 다른 글

[iOS] Swift 공부 - 17  (0) 2024.04.11
[iOS] Swift 공부 - 16  (0) 2024.04.04
[iOS] Swift 공부 - 14  (0) 2024.03.14
[iOS] Swift 공부 - 13  (0) 2024.03.07
[iOS] Swift 공부 - 12  (0) 2024.03.07