├── README.md └── docs ├── Common.md ├── SwiftUI.md └── UIKit.md /README.md: -------------------------------------------------------------------------------- 1 | # iOS Code Convention Guide(Swift) 2 | 3 | 해당 문서는 **채널톡** iOS 멤버들의 swift code convention을 맞추기 위해서 작성되었습니다. 팀원들의 합의에 따라서 언제든 바뀔 수 있으며, 가능한 해당 컨벤션을 맞춰주세요. 4 | 또한 컨벤션 관련 코드리뷰때는 해당 깃헙에서 항목의 링크를 붙여주면 커뮤니케이션에 좋습니다. 5 | 6 | 다음 Reference들을 참고하고, 수정해서 만들어졌습니다.([StyleShare](https://github.com/StyleShare/swift-style-guide), [raywenderlich](https://github.com/raywenderlich/swift-style-guide)) 7 | 8 | ## TODO 리스트 9 | - [ ] 현재까지 코드 컨벤션 추가하기 10 | - [ ] What is next? 11 | 12 | ## 목록 13 | * [공통](./docs/Common.md) - 모든 Swift 코드에서 준수해야 하는 컨벤션입니다. 14 | * [UIKit](./docs/UIKit.md) - UIKit 코드에서 준수해야 하는 컨벤션입니다. 15 | * [SwiftUI](./docs/SwiftUI.md) - SwiftUI 코드에서 준수해야 하는 컨벤션입니다. -------------------------------------------------------------------------------- /docs/Common.md: -------------------------------------------------------------------------------- 1 | # 공통 2 | 3 | ## 목차 4 | [한줄 최대 길이](#한-줄-최대-길이)
5 | [들여쓰기 규칙](#들여쓰기-규칙)
6 | [Guard 규칙](#guard-규칙)
7 | [final 규칙](#final-규칙)
8 | [접근자 규칙](#접근자-규칙)
9 | [함수정의 줄내림 규칙](#함수정의-줄내림-규칙)
10 | [Enum 줄내림 규칙](#enum-줄내림-규칙)
11 | [조건문 줄내림 규칙](#조건문-줄내림-규칙)
12 | [연산자 줄내림 규칙](#연산자-줄내림-규칙)
13 | [삼항연산자 규칙](#삼항연산자-규칙)
14 | [self 규칙](#self-규칙)
15 | [Array 선언 규칙](#array-선언-규칙)
16 | [메모리 관리 규칙](#메모리-관리-규칙)
17 | [클로저 사용 규칙](#클로저-사용-규칙)
18 | [Unwrapping 규칙](#unwrapping-규칙)
19 | [자주 사용되는 값 체크에 확장 변수 사용하기](#자주-사용되는-값-체크에-확장-변수-사용하기)
20 | [상수 선언 규칙](#상수-선언-규칙)
21 | [RxSwift 스케쥴러 지정 규칙](#rxswift-스케쥴러-지정-규칙)
22 | [VIPER 모듈 사이의 콜백 전달 규칙](#viper-모듈-사이의-콜백-전달-규칙)
23 | 24 | ## 코드 컨벤션 25 | 26 | ### 한 줄 최대 길이 27 | 28 | - 한 줄은 최대 120자를 넘지 않도록 합니다. 29 | - Xcode에서 **Preferences -> Text Editing -> Display -> Page guide at column** 부분을 120로 설정해서 사용해주세요. 30 | 31 | ### 들여쓰기 규칙 32 | 33 | - Indent는 2칸으로 지정합니다. 34 | - Xcode에서 **Preferences -> Text Editing -> Display -> Line wrapping** 부분을 2 spaces로 설정해서 사용해주세요. 35 | 36 | ### Guard 규칙 37 | 38 | - `guard`는 코드에서 분기를 빨리 끝낼 때, 과도한 조건문 복잡도가 생길 때 사용합니다. 39 | - `guard ~ else` 문이 한줄에 써진다면, 한줄로 사용합니다. 40 | - `Swift 5.7`부터 Shorthand syntax 사용이 가능하여 Optional Binding에 단축 구문을 사용합니다. 41 | 42 | ```swift 43 | // Preferred 44 | var number: Int? 45 | guard let number else { return } 46 | 47 | // Not Preffered 48 | var number: Int? 49 | guard let number = number else { return } 50 | ``` 51 | 52 | - `guard`의 condition이 하나이고, `else`가 여러줄이라면 다음과 같이 사용합니다. 53 | 54 | ```swift 55 | guard let number else { 56 | .... 57 | return 58 | } 59 | ``` 60 | 61 | - `guard`의 condition이 여러개라면 다음과 같이 사용합니다. ( 단, 최대 한줄로 작성이 가능한 경우는 한줄로 작성합니다. ) 62 | 63 | ```swift 64 | // Preferred 65 | guard let number, number > 0 else { 66 | .... 67 | return 68 | } 69 | 70 | guard 71 | let name, 72 | let number, 73 | isFavorited 74 | else { return } 75 | 76 | // Not Preffered 77 | guard let name, 78 | let number, 79 | isFavorited 80 | else { 81 | return 82 | } 83 | ``` 84 | 85 | - `guard`가 끝난 이후, 한 줄 띄우고 코드를 작성합니다. 86 | 87 | ```swift 88 | guard let number else { return } 89 | 90 | if number > 0 { 91 | ... 92 | } else { 93 | ... 94 | } 95 | ``` 96 | 97 | - `guard`가 중간에 오는 경우, 위 아래로 한줄씩 띄우고 코드를 작성합니다. 98 | 99 | ```swift 100 | let number: Int? = 0 101 | 102 | guard let number else { return } 103 | 104 | if number > 0 { 105 | ... 106 | } else { 107 | ... 108 | } 109 | ``` 110 | 111 | 112 | ### final 규칙 113 | 114 | - 더이상 상속이 일어나지 않는 class는 `final`을 붙여서 명시해줍니다. 115 | 116 | ```swift 117 | final class Channel { 118 | ... 119 | } 120 | ``` 121 | 122 | ### 접근자 규칙 123 | 124 | - class 내부에서만 쓰이는 변수는 `private`으로 명시해줍니다. 125 | - `fileprivate`는 필요한 경우가 아니면 피하고, `private`으로 써줍니다. 126 | 127 | ```swift 128 | final class Channel { 129 | private var number = 0 130 | ... 131 | } 132 | 133 | ### 함수정의 줄내림 규칙 134 | 135 | - 함수 정의가 길 경우 다음과 같이 줄내림 합니다. 136 | 137 | ```swift 138 | // Preferred 139 | func changeChannel( 140 | name: String, 141 | number: Int, 142 | isFavorited: Bool 143 | ) { 144 | ... 145 | } 146 | 147 | // Not Preferred 148 | func changeChannel( 149 | name: String, 150 | number: Int, 151 | isFavorited: Bool) { 152 | ... 153 | } 154 | ``` 155 | 156 | ### Enum 줄내림 규칙 157 | 158 | - 모든 `case` 내의 코드가 한줄이고 return 문이라면 붙여서 사용합니다. 159 | ```swift 160 | // Preferred 161 | switch channelNumber { 162 | case .main: return 0 163 | case .sub: return 1 164 | } 165 | 166 | // Not Preferred 167 | switch channelNumber { 168 | case .main: 169 | return 0 170 | case .sub: 171 | return 1 172 | } 173 | ``` 174 | 175 | - 단, switch 문 case 내 로직이 포함되는 경우 아래와 같이 줄내림 후 한 줄 띄우고 다음 case를 작성합니다. 176 | ```swift 177 | // Preferred 178 | switch checkChannel { 179 | case .main: 180 | guard channel.number != 0 else { return } 181 | 182 | channel.changeChannel(number: 0) 183 | 184 | case .sub: 185 | channel.changeChannel(number: 1) 186 | } 187 | 188 | // Not Preferred 189 | switch checkChannel { 190 | case .main: 191 | guard channel.number != 0 else { return } 192 | 193 | channel.changeChannel(number: 0) 194 | case .sub: 195 | channel.changeChannel(number: 1) 196 | } 197 | ``` 198 | 199 | ### 조건문 줄내림 규칙 200 | 201 | - `if` 나 `guard`에서 조건문이 여러개인 경우 줄이 길면 다음과 같이 줄내림 해줍니다. 202 | - `,`로 나뉠 땐 인덴트를 한번 넣어줍니다. 203 | - `||`, `&&`는 한줄 내에서 줄내림 된거처럼 한번 더 인덴트를 넣어줍니다. 204 | 205 | ```swift 206 | if let currentNumber, 207 | let number = channel.number, 208 | currentNumber > 0 209 | || number > 0 210 | || currentNumber == number, 211 | let isFavorited = isFavorited { 212 | .... 213 | } 214 | 215 | guard 216 | let currentNumber, 217 | let number = channel.number, 218 | currentNumber > 0 219 | || number > 0 220 | || currentNumber == number, 221 | let isFavorited = isFavorited { 222 | .... 223 | } 224 | 225 | ``` 226 | 227 | ### 연산자 줄내림 규칙 228 | 229 | - `+`, `||`, `&&` 등의 연산자에 대한 줄내림은 연산자를 같이 내려줍니다. 230 | 231 | ```swift 232 | // Preferred 233 | if number > 0 234 | || isFavorited == ture { 235 | ... 236 | } 237 | 238 | var name = "this" 239 | + " is" 240 | + " main" 241 | 242 | let isSuccess = !channel.isEmpty 243 | && isFavorited 244 | && channel.number > 0 245 | 246 | // Not Preferred 247 | if number > 0 || 248 | isFavorited == ture { 249 | ... 250 | } 251 | 252 | var name = "this" + 253 | " is" + 254 | " main" 255 | 256 | let isSuccess = !channel.isEmpty && 257 | isFavorited && 258 | channel.number > 5 259 | ``` 260 | 261 | ### 삼항연산자 규칙 262 | 263 | - `if ~ else`로 묶인 `return` 또는 값대입인 경우 삼항연산자로 줄일 수 있으면 줄여줍니다. 264 | 265 | ```swift 266 | // Preferred 267 | return number == 0 ? .main : .sub 268 | 269 | var channel = number == 0 ? .main : .sub 270 | 271 | // Not Preferred 272 | if number == 0 { 273 | return .main 274 | } else { 275 | return .sub 276 | } 277 | 278 | var result: Channel 279 | if number > 0 { 280 | result = .main 281 | } else { 282 | result = .sub 283 | } 284 | ``` 285 | 286 | - 줄내림이 필요한 경우 `?`를 기준으로 내려줍니다. 287 | 288 | ```swift 289 | // Preferred 290 | return number == 0 291 | ? .main : .sub 292 | 293 | // Not Preferred 294 | return number == 0 ? 295 | .main : .sub 296 | ``` 297 | 298 | - 조건에 따른 단순 분기일 때는 삼항연산자를 피해줍니다. 299 | 300 | ```swift 301 | // Preferred 302 | if isFavorited { 303 | channel.turnOn() 304 | } else { 305 | channel.turnOff() 306 | } 307 | 308 | // Not Preferred 309 | isFavorited ? channel.turnOn() : channel.turnOff() 310 | ``` 311 | 312 | - 삼항 연산자가 너무 길어질 경우 가독성을 위해 분리해줍니다. 313 | 314 | ```swift 315 | // Preferred 316 | let firstCondition = c == 2 ? d : e 317 | let secondCondition = b == 1 : firstCondition : f 318 | return test == 0 ? a : secondCondition 319 | // 또는 적절히 if를 나눠서 구현해준다. 320 | 321 | // Not Preferred 322 | return test == 0 323 | ? a 324 | : b == 1 325 | ? c == 2 326 | : d 327 | : e 328 | : f 329 | ``` 330 | 331 | ### self 규칙 332 | 333 | - 클래스와 구조체 내부에서는 `self`를 명시적으로 표시해줍니다. 334 | 335 | ### Array 선언 규칙 336 | 337 | - Array를 선언할 때는 다음과 같은 포맷을 지향합니다. 338 | 339 | ```swift 340 | // Preferred 341 | var managers: [Manager] = [] 342 | var counts: [String: Int] = [:] 343 | 344 | // Not Preferred 345 | var managers = [Manager]() 346 | var counts = [String: Int]() 347 | ``` 348 | 349 | ### 메모리 관리 규칙 350 | 351 | - Retain cycle이 발생하지 않도록 `weak self`를 이용합니다. 필요하다면 `guard let self = self else`를 통해서 unwrapping을 해줍니다. 352 | 353 | ```swift 354 | self.closePopup() { [weak self] _ in 355 | guard let self else { return } 356 | 357 | self.popAllController() 358 | } 359 | ``` 360 | 361 | ### 클로저 사용 규칙 362 | 363 | - 클로저의 파라미터는 괄호를 빼고 사용합니다. 364 | 365 | ```swift 366 | // Preferred 367 | { manager, user in 368 | ... 369 | } 370 | 371 | // Not Preferred 372 | { (manager, user) in 373 | ... 374 | } 375 | ``` 376 | 377 | - 클로져가 파라미터중 하나만 있고, 마지막에 항목이 클로져라면 파라미터 명을 생략해줍니다. 378 | ```swift 379 | // Preferred 380 | UIView.animate(withDuration: 0.25) { 381 | ... 382 | } 383 | 384 | // Not Preferred 385 | UIView.animate(withDuration: 0.25, animations: { () -> Void in 386 | ... 387 | }) 388 | ``` 389 | - 파라미터의 타입 정의는 가능하다면 생략해줍니다. 390 | 391 | ```swift 392 | // Preferred 393 | { manager, user in 394 | ... 395 | } 396 | 397 | // Not Preferred 398 | { (manager: Manager, user: User) -> Void in 399 | ... 400 | } 401 | ``` 402 | 403 | - 클로져 밖의 괄호는 가능한 생략해 줍니다. 404 | ```swift 405 | // Preferred 406 | self.channelView.snp.makeConstraints { 407 | $0.leading.equalToSuperview().inset(xMargin) 408 | $0.trailing.equalToSuperview().inset(xMargin) 409 | } 410 | 411 | // Not Preferred 412 | self.channelView.snp.makeConstraints ({ 413 | $0.left.equalToSuperview().offset(-xMargin) 414 | $0.right.equalToSuperview().offset(xMargin) 415 | }) 416 | ``` 417 | 418 | ### unwrapping 규칙 419 | 420 | - 최대한 force unwrapping은 피해줍니다. 옵셔널(`?`)의 경우는 optional chaining 등으로 풀어서 사용해주시고 `!`는 최대한 피해줍니다. 421 | 422 | ```swift 423 | // Preferred 424 | func getResultText(with text: String?) -> String { 425 | if let resultText = text { 426 | return resultText 427 | } 428 | ... 429 | } 430 | 431 | // Not Preferred 432 | func getResultText(with text: String?) -> String { 433 | return text! 434 | ... 435 | } 436 | ``` 437 | 438 | ### 자주 사용되는 값 체크에 확장 변수 사용하기 439 | 440 | - `nil`이나 `0`과 같은 값을 체크할 때 정의된 확장 변수가 있다면 등호/부등호를 사용하는 대신 해당 확장 변수를 사용합니다. 441 | 442 | ```swift 443 | // Preferred 444 | if optionalValue.isNil { ... } 445 | if optionalValue.isNotNil { ... } 446 | if numberValue.isZero { ... } 447 | if optionalBoolValue.beTrue { ... } 448 | if optionalBoolValue.beFalse { ... } 449 | 450 | // Not Preferred 451 | if optionalValue == nil { ... } 452 | if optionalValue != nil { ... } 453 | if numberValue == 0 { ... } 454 | if optionalBoolValue == true { ... } 455 | if optionalBoolValue == false { ... } 456 | ``` 457 | 458 | ### 상수 선언 규칙 459 | 460 | - 코드 상단에 `private`로 정의하여 일반적인 상수를 단순히 정의할때는 `struct` 대신 `enum`을 사용해줍니다. 461 | Generic 사용 시 class 내부에서 static 선언이 어렵기 때문에 class 밖에서 사용합니다. 462 | 463 | - Snapkit 등에서 autolayout을 설정할 때 상수는 위쪽에 `Metric`으로 빼줍니다. 464 | - 여러번 쓰이는 폰트는 `Font`로 빼줍니다 465 | - 테이블뷰 등의 Section 관련은 `Section`으로 빼줍니다. 466 | - 테이블뷰 등의 row 관련은 `Row`로 빼줍니다. 467 | - 그외 내부적으로 쓰이는 상수는 `Constant`로 빼줍니다. 468 | 469 | ```swift 470 | private enum Metric { 471 | static let avatarLength = 3.f 472 | ... 473 | } 474 | 475 | private enum Font { 476 | static let titleLabel = UIFont.boldSystemFont(ofSize: 14) 477 | ... 478 | } 479 | 480 | private enum Section { 481 | static let managers = 0 482 | static let users = 1 483 | ... 484 | } 485 | 486 | private enum Row { 487 | static let name = 0 // managers 섹션의 0번째 row 488 | static let username = 0 // users 섹션의 0번째 row 489 | ... 490 | } 491 | 492 | private enum Constant { 493 | static let maxLinesWithOnlyText = 2 494 | ... 495 | } 496 | ``` 497 | - Localize 상수는 기존 상수 규칙과 다르게, enum - case로 정리합니다. 규칙은 다음과 같습니다. 498 | ```swift 499 | private enum Localized { 500 | case leavedThread 501 | case managersCount(String) 502 | ... 503 | 504 | var rawValue: String { 505 | switch self { 506 | case .leaveThread: return "thread.header.leave_thread".localized 507 | case .managersCount(let count): return "team_chat_info.manager_list.title".localized(with: count) 508 | ... 509 | } 510 | } 511 | } 512 | ``` 513 | 514 | ### RxSwift 스케쥴러 지정 규칙 515 | 516 | - `Observable`에 대해서 `subscribe(on:)`과 `observe(on:)` 함수를 호출해 어떤 어떤 스케쥴러에서 작동할지 지정할 수 있는데, 이를 `Observable`을 생성하는 곳이 아닌 생성된 `Observable`을 사용하는 곳에서 지정합니다. 517 | - 자세한 내용은 ["SubscribeOn / ObserveOn 논의 정리"](https://www.notion.so/channelio/SubscribeOn-ObserveOn-dfd918eee039412ea3ac9d72d3da08fd?pvs=4)를 확인해주세요. 518 | 519 | ```swift 520 | // Preferred 521 | func someObservable() -> Observable { ... } 522 | 523 | someObservable() 524 | .subscribe(on: ConcurrentDispatchQueueScheduler(qos: .background)) 525 | .observe(on: MainScheduler.instance) 526 | .subscribe { _ in 527 | ... 528 | } 529 | 530 | // Not Preferred 531 | Observable 532 | .create { ... } 533 | .subscribe(on: ConcurrentDispatchQueueScheduler(qos: .background)) 534 | .observe(on: MainScheduler.instance) 535 | ... 536 | ``` 537 | 538 | ### VIPER 모듈 사이의 콜백 전달 규칙 539 | 540 | - VIPER 모듈이 다른 VIPER 모듈을 생성하면서 생성한 모듈로부터 어떤 동작이 완료되었다는 콜백을 받고 싶을 때, 이를 클로져를 사용하기보다는 `PublishRelay`, `PublishSubject`와 같은 옵저버 타입을 전달하도록 합니다. 541 | 542 | ```swift 543 | // Preferred 544 | extension CreateValueRouter { 545 | static func createModule(createValueSignal: PublishRelay) { 546 | ... 547 | } 548 | } 549 | 550 | class CreateValueModuleClass { 551 | var createValueSignal: PublishRelay? 552 | ... 553 | func valueCreated(_ value: Value) { 554 | self.createValueSignal?.accept(value) 555 | } 556 | } 557 | 558 | // Not Preferred 559 | extension CreateValueRouter { 560 | static func createModule(valueCreated: (Value) -> Void) { 561 | ... 562 | } 563 | } 564 | 565 | class CreateValueModuleClass { 566 | var valueCreated: ((Value) -> Void)? 567 | ... 568 | func valueCreated(_ value: Value) { 569 | self.valueCreated?(value) 570 | } 571 | } 572 | ``` -------------------------------------------------------------------------------- /docs/SwiftUI.md: -------------------------------------------------------------------------------- 1 | # SwitfUI 2 | 3 | ## 목차 4 | [SubView 선언 규칙](#subview-선언-규칙)
5 | [SwiftUI 이름 규칙](#swiftui-이름-규칙)
6 | [Spacer() 사용 규칙](#spacer-사용-규칙)
7 | [뷰 Property 규칙](#뷰-property-규칙)
8 | 9 | ## 코드 컨벤션 10 | 11 | ### SubView 선언 규칙 12 | 13 | - `@ViewBuilder` 함수 사용보다 `struct`를 통해 SubView 선언합니다. 14 | - 부모뷰에 있는 `@State`를 변경하기 위해 `@ViewBuilder` 를 통해 함수 선언이 가능하지만, 위계를 명확하게 하기 위해 `@Binding`을 사용합니다. 15 | 16 | ```swift 17 | // Preferred 18 | struct SubView: View { 19 | @Binding private var isFavorited: Bool 20 | 21 | init(isFavorited: Binding) { 22 | self._isFavorited = isFavorited 23 | } 24 | 25 | var body: some View { 26 | ... 27 | } 28 | } 29 | 30 | // Not Preferred 31 | @ViewBuilder 32 | public func subView(title: String) -> some View { 33 | VStack { 34 | ... 35 | Button(title) { self.isFavorited.toggle() } 36 | } 37 | } 38 | ``` 39 | 40 | ### SwiftUI 이름 규칙 41 | 42 | - `SwiftUI`에서는 명확히 View가 아닌 이상 suffix 없이 사용합니다. 43 | 44 | ```swift 45 | // Preferred 46 | struct ChannelButton: View { 47 | ... 48 | } 49 | 50 | struct ChannelSwitch: View { 51 | ... 52 | } 53 | 54 | // Not Preferred 55 | struct ChannelButtonView: View { 56 | ... 57 | } 58 | 59 | struct ChannelSwitchView: View { 60 | ... 61 | } 62 | ``` 63 | 64 | ### Spacer() 사용 규칙 65 | 66 | - 단순한 뷰 크기 확장을 위한 경우 `Spacer()` 사용보다 `.frame()`를 사용합니다. 67 | - 수직 확장은 `maxHeight: .infinity`, 수평 확장은 `maxWidth: .infinity` 를 사용합니다. 68 | 69 | ```swift 70 | // Preferred 71 | HStack { 72 | ... 73 | } 74 | .frame(maxWidth: .infinity, alignment: .leading) 75 | 76 | // Not Preferred 77 | HStack { 78 | ... 79 | Spacer() 80 | } 81 | ``` 82 | 83 | ### 뷰 Property 규칙 84 | 85 | - 필수 Property는 init 시점에 적용합니다. 옵셔널한 Property는 ViewModifier함수로 참조합니다. 86 | - Button과 같이 액션이 따르는 View는 init 시점에 액션 함수를 받지만, 액션이 필수가 아닌 뷰에서는 ViewModifier함수로 옵셔널하게 관리합니다. 87 | 88 | ```swift 89 | // Required Action 90 | struct ChannelButton: View { 91 | private let title: String 92 | private let action: () -> () 93 | 94 | init(title: String, action: @escaping () -> ()) { 95 | self.title = title 96 | self.action = action 97 | } 98 | ... 99 | } 100 | 101 | // Optional Action 102 | struct ChannelView: View { 103 | private let title: String 104 | private let optionalAction: (() -> ())? 105 | 106 | init(title: String) { 107 | self.title = title 108 | } 109 | ... 110 | 111 | func optionalAction(_ optionalAction: (() -> ())?) -> Self { 112 | var view = self 113 | view.optionalAction = optionalAction 114 | return view 115 | } 116 | } 117 | ``` -------------------------------------------------------------------------------- /docs/UIKit.md: -------------------------------------------------------------------------------- 1 | # UIKit 2 | 3 | ## 목차 4 | [Snapkit 관련 규칙](#snapkit-관련-규칙)
5 | [Metric 명명 규칙](#metric-명명-규칙)
6 | [super 함수 호출시 줄내림 규칙](#super-함수-호출시-줄내림-규칙)
7 | [View property 이니셜라이징 규칙](#view-property-이니셜라이징-규칙)
8 | [Subview 추가 규칙](#subview-추가-규칙)
9 | 10 | ## 코드 컨벤션 11 | 12 | ### Snapkit 관련 규칙 13 | 14 | - superView, safeArea에 대해서는 `inset`을, 다른 뷰에 대해서는 `offset`을 지향해주세요. 15 | - layout warning이 나면 모두 없애주시길 바랍니다. 16 | - `right`, `left`보단 `leading`, `trailing`을 사용해주세요. 17 | - 상위 뷰와 관계를 설정할 때는 `equalToSuperview()`를 사용해주세요. 18 | - 동적으로 레이아웃을 변경해야 할 때는 별개의 `Constraint` 참조를 생성하기보다는 해당 뷰에 대한 `updateConstraints` 함수를 호출해서 수정해주세요. 19 | 20 | 21 | ```swift 22 | // Preferred 23 | self.channelView.snp.makeConstraints { 24 | $0.leading.equalToSuperview().inset(xMargin) 25 | $0.trailing.equalToSuperview().inset(xMargin) 26 | } 27 | 28 | self.channelView.snp.makeConstraints { 29 | $0.directionalEdges().inset(xMargin) 30 | } 31 | 32 | self.channelView.snp.updateConstraints { 33 | $0.bottom.equalToSuperview().inset(keyboardHeight) 34 | } 35 | 36 | // Not Preferred 37 | self.channelView.snp.makeConstraints { 38 | $0.left.equalToSuperview().offset(-xMargin) 39 | $0.right.equalToSuperview().offset(xMargin) 40 | } 41 | 42 | self.channelView.snp.makeConstraints { 43 | $0.edges().inset(xMargin) 44 | } 45 | 46 | var constraint: Constraint? 47 | ... 48 | self.channelView.snp.makeConstraints { 49 | constraint = $0.bottom.equalToSuperview().inset(0).constraint 50 | } 51 | constraint?.update(inset: 10) 52 | ``` 53 | 54 | ### Metric 명명 규칙 55 | 56 | - Metric은 다음과 같은 규칙을 따릅니다. 57 | - 상수로서의 의미로 계산의 편의를 위해 양수로 지정합니다.(사용하는 곳에서 방향에 관련된 값이나 -를 붙여줍니다.) 58 | 59 | ```swift 60 | private enum Metric { 61 | static let imageViewTop = 3.f 62 | } 63 | 64 | self.imageView.snp.makeConstraints { 65 | $0.top.equalToSuperview().inset(-Metric.imageViewTop) 66 | } 67 | ``` 68 | 69 | - View와 View의 관계를 명명에 표현하지 않습니다. 70 | - 해당 View에 대해 View 이름(선언한 변수명) 또는 대상을 prefix로, 71 | (Top, Bottom, Leading, Trailing, center, centerX, centerY, Edge, Length, Width, Height)을 postfix로 72 | 붙여 나타내줍니다. 73 | - Padding, Margin, Inset, Offset 이라는 단어는 붙이지 않습니다. 74 | - 두가지를 한번에 명명할때는 (Top -> Bottom -> Leading -> Trailing) 4개만 해당 순서대로 묶어서 표현하며, 4개에 모두 사용될시 Edge를 사용합니다. 75 | - 가로 세로 길이가 같다면 Size 대신에 Length로 표현해줍니다. 가로 세로길이가 다르다면 Width, Height로 합니다. 76 | 77 | ```swift 78 | // Preferred 79 | private enum Metric { 80 | static let imageViewTop = 3.f 81 | static let nameLabelTopBottom = 3.f 82 | static let avatarViewTopLeading = 3.f 83 | static let textViewLeadingTrailing = 3.f 84 | static let imageViewEdge = 6.f 85 | static let imageViewLength = 7.f 86 | } 87 | 88 | // Not Preferred 89 | private enum Metric { 90 | static let imageViewNameLabelMargin = 3.f 91 | static let imageViewTopBottomLeadingTrailing = 3.f 92 | static let imageViewSize = 7.f 93 | } 94 | ``` 95 | 96 | ### super 함수 호출시 줄내림 규칙 97 | 98 | - 어떤 클래스를 상속하는 클래스가 super의 함수를 호출할 때 바로 아래에 빈 라인을 추가합니다. 99 | 100 | ```swift 101 | // Preferred 102 | override func viewDidLoad() { 103 | super.viewDidLoad() 104 | 105 | self.callSomeFunction() 106 | } 107 | 108 | // Not Preferred 109 | override func viewDidLoad() { 110 | super.viewDidLoad() 111 | self.callSomeFunction() 112 | } 113 | ``` 114 | 115 | ### View property 이니셜라이징 규칙 116 | 117 | - 뷰나 객체들을 initialize할때 가능하면 `then`을 이용해서 초기에 해줄 수 있는 설정들은 최대한 해줍니다. 118 | 119 | ```swift 120 | let nameLabel = UILabel().then { 121 | $0.text = "manager name" 122 | } 123 | ``` 124 | 125 | ### Subview 추가 규칙 126 | 127 | - `UIView`나 `UIStackView`에 복수의 하위 뷰를 추가할 때 되도록이면 `addSubviews`, `addArrangedSubviews`를 사용합니다. 128 | 129 | ```swift 130 | // Preferred 131 | self.containerView.addSubviews(self.titleView, self.imageView) 132 | self.stackView.addArrangedSubviews(self.topView, self.bottomView) 133 | 134 | // Not Preferred 135 | self.containerView.addSubview(self.titleView) 136 | self.containerView.addSubview(self.imageView) 137 | self.stackView.addArrangedSubviews(self.topView) 138 | self.stackView.addArrangedSubviews(self.bottomView) 139 | ``` --------------------------------------------------------------------------------