Skip to content

๐ŸŽ SOPT27 iOS ์‹ ์œค์•„ ๐ŸŽ

Notifications You must be signed in to change notification settings

27thONSOPT-iOS/ShinYoonAh

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

47 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

ShinYoonAh

๐Ÿ” 6์ฃผ์ฐจ ๊ณผ์ œ (11/21_์ œ์ถœ ์™„๋ฃŒ) ๐Ÿ”

  • [์ผ๋ฐ˜ ๊ณผ์ œ]

    โ–ถ๏ธ 6์ฃผ์ฐจ ์„ธ๋ฏธ๋‚˜ ๊ณผ์ œ์—์„œ ํšŒ์›๊ฐ€์ž…๊ณผ ๋กœ๊ทธ์ธ์ด ์ž˜ ์ง„ํ–‰๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ๊ณผ์ œ ๐Ÿ”ฅ

    ๐Ÿ˜Ž ๊ทธ ์ „์— ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์— ๋Œ€ํ•ด์„œ ์ •๋ฆฌํ•˜๋Š” ์‹œ๊ฐ„ ๐Ÿ˜Ž

    1. POST : ๋ฐ์ดํ„ฐ๋ฅผ BODY์— ์ˆจ๊ฒจ ์„œ๋ฒ„์— ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹, ๋ฐ์ดํ„ฐ์˜ ์ „์†ก๊ณผ ๋”๋ถˆ์–ด ๊ฐฑ์‹ , ์‚ฝ์ž…์—๋„ ์‚ฌ์šฉ๋œ๋‹ค.
    2. GET : URL์— ๋ณ€์ˆ˜๋ฅผ ํฌํ•จ์‹œ์ผœ ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๋Š” ๋ฐฉ์‹, ๋ฆฌ์†Œ์Šค๋ฅผ ์ฝ์„ ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.
    3. PUT : ํ•ด๋‹น ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ์˜ ์ˆ˜์ •์— ์ฃผ๋กœ ์‚ฌ์šฉ
    4. DELETE : ํ•ด๋‹น ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ์˜ ์‚ญ์ œ์— ์ฃผ๋กœ ์‚ฌ์šฉ
    • REST API

      โžฐ REST : REST(Representational State Transfer)๋Š” ๋„คํŠธ์›Œํฌ ์ž์›์„ ์ •์˜ํ•˜๊ณ  ์ž์›์— ๋Œ€ํ•œ ์ฃผ์†Œ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•, HTTP ๊ธฐ๋ฐ˜์œผ๋กœ ํ•„์š”ํ•œ ์ž์›์— ์ ‘๊ทผํ•˜๋Š” ๋ฐฉ์‹์„ ์ •ํ•ด๋†“์€ ์•„ํ‚คํ…์ณ

      ๐Ÿ‘‰ ํ–‰์œ„(Verb): HTTP Method
      ๐Ÿ‘‰ ์ž์›(Resource): URI
      ๐Ÿ‘‰ ๋ฉ”์‹œ์ง€(Representation): { โ€œageโ€: 35 } ๊ณผ ๊ฐ™์€ JSON ํฌ๋งท์˜ ๋ฐ์ดํ„ฐ

      ~> URI๋กœ ์ ‘๊ทผ

      ~> ์ „๋‹ฌ ๋ฐฉ์‹์œผ๋กœ HTTP Method๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์กฐํšŒ/์‚ฝ์ž…/๊ฐฑ์‹ /์‚ญ์ œ ์ˆ˜ํ–‰

    • JSON(JavaScript Object Notation)

      โžฐ ์ฃผ๊ณ  ๋ฐ›์„ ์ˆ˜ ์ž‡๋Š” ์ž๋ฃŒํ˜•์€ Int, String, Boolean, Array, Object

      โžฐ Key ์™€ Value๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ์œผ๋ฉฐ ๊ฐ์ฒด๋Š” ์ค‘๊ด„ํ˜ธ {}, ๋ฐฐ์—ด์€ ๋Œ€๊ด„ํ˜ธ []๋กœ ๊ฐ์‹ผ๋‹ค.

    ๐Ÿ”ฅ ํ†ต์‹ ์„ ์œ„ํ•ด์„œ Alamofire๋ฅผ pod install

    โš™๏ธ ํ†ต์‹  ์‹ค์Šต์„ ์œ„ํ•œ ์„ค์ •

    1. Info.plist

    2. Information Property List โ†’ '+' button โ†’ App Transport Security Settings โ†’ '+' button โ†’ Allow Arbitrary Loads โ†’ 'Yes'

    3. API ๋ฌธ์„œ ํ†ตํ•ด์„œ URL ์ฃผ์†Œ ํ™•์ธ

    4. APIConstants.swift โ†’ API ์ฃผ์†Œ ๋ชจ์•„๋‘๋Š” ๊ณณ

      struct APIConstants {
      		static let baseURL = "http://15.164.83.210:3000"
      		static let usersSignInURL = baseURL + "/users/signin"
      		static let usersSignUpURL = baseURL + "/users/signup" 
      }
    5. GenericResponse.swift โ†’ Codable : ๋ฐ์ดํ„ฐ๋ฅผ JSON ๋ฐ์ดํ„ฐ ํฌ๋งท์œผ๋กœ ์ž์œ ๋กญ๊ฒŒ Decoding, Encoding ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ๋Š” protocol

      struct GenericResponse<T: Codable>: Codable { 
          var status: Int
          var success: Bool
          var message: String
          var data: T?
      
      		// JSON ๋ฐ์ดํ„ฐ์˜ ํ‚ค ์™€ swift struct์˜ ๋ณ€์ˆ˜๋ฅผ ๋งตํ•‘ํ•˜๋Š” ์—ญํ• 
          enum CodingKeys: String, CodingKey {
              case status = "status"
              case success = "success"
              case message = "message"
              case data = "data"
          }
      
      		// data์— ๋Œ€ํ•œ ํ‚ค ๊ฐ’์ด ์žˆ๋Š” ๊ฒฝ์šฐ์™€ ์—†๋Š” ๊ฒฝ์šฐ๋ฅผ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•˜๋Š” ์—ญํ• 
          init(from decoder: Decoder) throws {
              let values = try decoder.container(keyedBy: CodingKeys.self)
              status = (try? values.decode(Int.self, forKey: .status)) ?? -1
              success = (try? values.decode(Bool.self, forKey: .success)) ?? false
              message = (try? values.decode(String.self, forKey: .message)) ?? ""
              data = (try? values.decode(T.self, forKey: .data)) ?? nil
      		} 
      }
    6. data๋ฅผ ํ†ตํ•ด ๋“ค์–ด์˜ฌ ํƒ€์ž…๋„ ๋งŒ๋“ค์–ด ์ฃผ๊ธฐ โ†’ SignInData.swift

    7. https://quicktype.io/ : JSON ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๊ฐ„ํŽธํ•˜๊ฒŒ swift์˜ struct๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ์‚ฌ์ดํŠธ

    8. ๋ฐ์ดํ„ฐ ๋ฐ›๊ธฐ๋ฅผ ์„ฑ๊ณตํ–ˆ์„ ๋•Œ ๋ฐ›์•„์˜ฌ ํƒ€์ž… ์ƒ์„ฑ(SignInData.swift)

      struct SignInData: Codable {
          var email, password, userName: String
      }
    9. NetworkResult.swift โ†’ ์„œ๋ฒ„ ํ†ต์‹ ์— ๋”ฐ๋ฅธ ๊ฒฐ๊ณผ

      // ์„œ๋ฒ„ ํ†ต์‹ ์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ(์„ฑ๊ณต, ์š”์ฒญ์—๋Ÿฌ, ๊ฒฝ๋กœ์—๋Ÿฌ, ์„œ๋ฒ„๋‚ด๋ถ€์—๋Ÿฌ, ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ ์‹คํŒจ)
      enum NetworkResult<T> {
          case success(T)
          case requestErr(T)
          case pathErr
          case serverErr
          case networkFail
      }
    10. AuthService.swift โ†’ ๋กœ๊ทธ์ธ ์„œ๋ฒ„ ํ†ต์‹  ๊ตฌํ˜„์„ ์œ„ํ•œ ๊ตฌ์กฐ์ฒด

      import Foundation
      import Alamofire
      
      struct AuthService {
      	// ์‹ฑ๊ธ€ํ†ค ๊ฐ์ฒด๋กœ ์•ฑ ์–ด๋””์„œ๋“  ์ ‘๊ทผ ๊ฐ€๋Šฅ
          	static let shared = AuthService()
      		
      	func signIn(email: String,
      			password: String,
      			completion: @escaping (NetworkResult<Any>) -> (Void)) {
      		// ํ†ต์‹  url, ์š”์ฒญ ํ—ค๋”, ์š”์ฒญ ๋ฐ”๋””
      		let url = APIConstants.usersSignInURL
      		let header: HTTPHeaders = [
      			"Content-Type":"application/json"
      		]
      		let body: Parameters = [
      			"email": email,
      			"password":password
      		]
      		// ์›ํ•˜๋Š” ํ˜•์‹์˜ HTTP Request ์ƒ์„ฑ
      		let dataRequest = AF.request(url,
                                   		method: .post,
                                   		parameters: body,
                                   		encoding: JSONEncoding.default,
                                   		headers: header)
      		// ๋ฐ์ดํ„ฐ ํ†ต์‹  ์‹œ์ž‘            ๋ฐ์ดํ„ฐ ํ†ต์‹  ๊ฒฐ๊ณผ
      		dataRequest.responseData { (response) in
      			// ํ†ต์‹ ์˜ ์„ฑ๊ณต์‹คํŒจ์— ๋”ฐ๋ฅธ ๋ถ„๊ธฐ์ฒ˜๋ฆฌ
      			switch response.result {
      			case .success:
      				// ํ†ต์‹ ์˜ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ statusCode์™€ value ๊ฐ’์„ ๊ฐ€์ง€๊ฒŒ ๋จ
      				guard let statusCode = response.response?.statusCode else { 
      					return
      				}
      				guard let data = response.value else {
      					return
      				}
      				// Completion์ด๋ž€ ํด๋กœ์ ธ์—๊ฒŒ ์ „๋‹ฌํ•  ๋ฐ์ดํ„ฐ๋ฅผ judgeSignInData๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๊ฒฐ์ •
      				completion(judgeSignInData(status: statusCode, data: data))
      		       case .failure(let err):
      	            		print(err)
      				completion(.networkFail) 
      			}
      		}
      	}
      	// statusCode์™€ decode ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ NetworkResult๋ฅผ ๋ฐ˜ํ™˜์‹œ์ผœ์ค€๋‹ค.
      	private func judgeSignInData(status: Int, data: Data) -> NetworkResult<Any> {
      		// ํ†ต์‹ ์„ ํ†ตํ•ด ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ decode
      		let decoder = JSONDecoder()
      		guard let decodedData = try? decoder.decode(GenericResponse<SignInData>.self, from: data) else {
      			return .pathErr
      		}
      		// statusCode๋ฅผ ํ†ตํ•ด ํ†ต์‹  ๊ฒฐ๊ณผ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
      		switch status {
      			// ์„ฑ๊ณต์ ์œผ๋กœ ํ†ต์‹ ์— ์„ฑ๊ณต, ์„ฑ๊ณตํ–ˆ๋‹ค๋Š” ๊ฒฐ๊ณผ์™€ ํ•จ๊ป˜ decodeํ•œ data๊ฐ’๋„ ์ „๋‹ฌํ•ด ์ค€๋‹ค.
      			case 200:
      				return .success(decodedData.data) 
      			// ํ†ต์‹ ์—๋Š” ์„ฑ๊ณตํ–ˆ์ง€๋งŒ, ์š”์ฒญ๊ฐ’์— ๋Œ€ํ•œ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜ ๊ฒฐ๊ณผ์™€ ํ•จ๊ป˜ ์˜ค๋ฅ˜ ๋ฉ”์„ธ์ง€๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.
      			case 400..<500:
      				return .requestErr(decodedData.message) 
      			// Server ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง€์ •ํ•œ Server์ƒ ์˜ ์—๋Ÿฌ ์ฝ”๋“œ ์—๋Ÿฌ ๊ฒฐ๊ณผ๋งŒ์„ ๋ณด๋‚ด์ค€๋‹ค.
      			case 500:
      			        return .serverErr
      			default:
      			        return .networkFail
      		}
      	}
      }


    1๏ธโƒฃ ํšŒ์›๊ฐ€์ž… ํ†ต์‹  ํ•˜๊ธฐ

    1. APIConstants.swift โ†’ signUp URL ๋„ฃ์–ด์ฃผ์ž

    2. AuthService.swift โ†’ signUp ํ•จ์ˆ˜๋ฅผ ๋„ฃ์–ด์ฃผ์ž(userName๋ฅผ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— userNameํ•ญ๋ชฉ ์ถ”๊ฐ€)

      func signUp(email: String, password: String, userName: String, completion: @escaping (NetworkResult<Any>) -> (Void)) {
              let url = APIConstants.usersSignUpURL
              
              let header: HTTPHeaders = [
                  "Content-Type":"application/json"
              ]
              
              let body: Parameters = [
                  "email": email,
                  "password": password,
                  "userName": userName
              ]
              
              let dataRequest = AF.request(url,
                                           method: .post,
                                           parameters: body,
                                           encoding: JSONEncoding.default,
                                           headers: header)
              
              dataRequest.responseData { (response) in
                  switch response.result {
                  case .success:
                      guard let statusCode = response.response?.statusCode else {
                          return
                      }
                      guard let data = response.value else {
                          return
                      }
                      completion(judgeSigninData(status: statusCode, data: data))
                  case .failure(let err):
                      print(err)
                      completion(.networkFail)
                  }
              }
              
          }
    3. ํšŒ์› ๊ฐ€์ž… ํ•˜๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ โ†’ ํ†ต์‹ ์ด ์ด๋ค„์ง€๋„๋ก ํ•˜๊ธฐ

      guard let emailText = emailTextField.text,
                    let password = passwordTextField.text,
                    let name = userNameTextField.text else {
                  return
              }
              
              AuthService.shared.signUp(email: emailText,
                                        password: password, userName: name) { (networkResult) in
                  switch networkResult {
                  case .success(let data):
                      if let data = data as? SignInData {
                          self.simpleAlert(title: "ํšŒ์› ๊ฐ€์ž… ์„ฑ๊ณต", message: "\(data.userName)๋‹˜ ํšŒ์› ๊ฐ€์ž… ์„ฑ๊ณต!")
                          UserDefaults.standard.set(data.userName, forKey: "userName")
                          self.presentingViewController?.dismiss(animated: true, completion: nil)
                      }
                      print("success")
                  case .requestErr(let msg):
                      if let message = msg as? String {
                          self.simpleAlert(title: "ํšŒ์› ๊ฐ€์ž… ์‹คํŒจ", message: message)
                      }
                      print("requestErr")
                  case .pathErr:
                      print("pathErr")
                  case .serverErr:
                      print("serverErr")
                  case .networkFail:
                      print("networkFail")
                  }
              }

      โ†’ simpleAlert ์‚ฌ์šฉํ•ด์„œ ๋ฉ”์‹œ์ง€ ์ฐฝ์ด ๋„์›Œ์งˆ ์ˆ˜ ์žˆ๋„๋ก ํ•˜์ž




๐Ÿฅ 4์ฃผ์ฐจ ๊ณผ์ œ (11/12_์ œ์ถœ ์™„๋ฃŒ) ๐Ÿฅ

  • ์ผ๋ฐ˜ ๊ณผ์ œ

    โ–ถ๏ธ TextField๋ฅผ ๋ˆŒ๋Ÿฌ์„œ ํ‚ค๋ณด๋“œ๊ฐ€ ๋‚˜์˜ค๋ฉด ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์˜ฌ๋ ธ๋‹ค๊ฐ€ return๋ฅผ ๋ˆ„๋ฅด๋ฉด ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋‚ด๋ ค๋ผ

    โ†’ textfield๋ฅผ ๋ˆ„๋ฅด๋Š” ๊ฒƒ๊ณผ return ํ‚ค๋ฅผ ๋ˆ„๋ฅด๋Š” ๊ฒƒ ๋ชจ๋‘ UITextFieldDelegate๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ตฌํ˜„ํ•˜์ž!

    extension LoginViewController: UITextFieldDelegate {
        func textFieldDidBeginEditing(_ textField: UITextField) {
            let move = CGAffineTransform(translationX: 0, y: -50)
    
            UIView.animate(withDuration: 0.2, animations: {
                self.profileImage.transform = move
                self.partLabel.transform = move
                self.partTextField.transform = move
                self.nameLabel.transform = move
                self.nameTextField.transform = move
            })
        }
    
        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            UIView.animate(withDuration: 0.2, animations: {
                self.profileImage.transform = .identity
                self.partLabel.transform = .identity
                self.partTextField.transform = .identity
                self.nameLabel.transform = .identity
                self.nameTextField.transform = .identity
            })
            textField.resignFirstResponder()
            return true
        }
    }
    • UITextFieldDelegate์— ์žˆ๋Š” textFieldDidBeginEditing, textFieldShouldReturn๋ฅผ ์‚ฌ์šฉ
      • textFieldDidBeginEditing : textField ์ˆ˜์ •์ด ์‹œ์ž‘๋  ๋•Œ ๋™์ž‘ โ†’ textField๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋™์ž‘
      • textFieldShouldReturn : textField์˜ returnํ‚ค๋ฅผ ๋ˆŒ๋ €์„ ๋•Œ ๋™์ž‘
    • CGAffineTransform์˜ translaionX๋ฅผ ์‚ฌ์šฉํ•ด์„œ y์ขŒํ‘œ๋ฅผ 50 ์˜ฌ๋ฆฌ์ž โ†’ animateํ•จ์ˆ˜์— ๋„ฃ์–ด์ฃผ๋ฉด ์ด๋ฏธ์ง€๊ฐ€ ์˜ฌ๋ผ๊ฐ€๋Š” ๋™์ž‘์ด ์™„์„ฑ
    • ๋‹ค์‹œ ๋‚ด๋ ค์˜ค๋Š” ๋™์ž‘์€ .identity๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์›๋ž˜ ์ž๋ฆฌ๋กœ ๋Œ์•„์˜ค๊ฒŒ ํ•ด์ฃผ์ž
    • resignFirstResponder ๋Š” ํ‚ค๋ณด๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ๋‚ด๋ ค๊ฐ€๋„๋ก ๋™์ž‘ํ•จ

  • ๋„์ „ ๊ณผ์ œ

    โ–ถ๏ธ ์Šคํฌ๋กค์„ ํ•  ๊ฒฝ์šฐ ๊ณ ์ • ํ—ค๋”๊ฐ€ ์‚ฌ๋ผ์กŒ๋‹ค๊ฐ€ ๋ฉˆ์ถ”๋ฉด ๊ณ ์ • ํ—ค๋”๊ฐ€ ๋‚˜ํƒ€๋‚˜๊ฒŒ ํ•˜์ž โ—๏ธ

    โ†’ UIScrollViewDelegate๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ—ค๋”์˜ ์›€์ง์ž„์„ ๊ตฌํ˜„ํ•˜์ž!

    extension ViewController: UIScrollViewDelegate {
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            UIView.animate(withDuration: 0.3, animations: {
                if self.isTop == false {
                    self.headerView.transform = CGAffineTransform(translationX: 0, y: -88)
                }
                if scrollView.contentOffset.y > 0 {
                    self.isTop = false
                    self.scrollView.frame.size.height = UIScreen.main.bounds.size.height
                    self.scrollView.transform = CGAffineTransform(translationX: 0, y: -88)
                } else {
                    self.isTop = true
                }
            })
        }
        
        
        func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
            UIView.animate(withDuration: 0.3, animations: {
                if scrollView.contentOffset.y >= 0 {
                    self.headerView.transform = .identity
                    self.scrollView.transform = .identity
                }
            })
        }
    }
    • UIScrollViewDelegate์— ์žˆ๋Š” scrollViewDidScroll๊ณผ scrollViewDidEndDecelerating๋ฅผ ์‚ฌ์šฉ

      • scrollViewDidScroll : scrollView๊ฐ€ scroll๋˜๋ฉด ๋™์ž‘

      • scrollViewDidEndDecelerating : scrollView๊ฐ€ scroll๋˜๋‹ค๊ฐ€ scroll๋˜๋Š” ์†๋„๊ฐ€ ๊ฐ์†๋˜๋ฉด ๋™์ž‘

        โ†’ ์ด๊ฑด ๊ทธ๋ƒฅ ๋ฉˆ์ถ”๋ฉด ํ—ค๋” ๋‚˜ํƒ€๋‚˜๋Š” ๋™์ž‘์ด ์•ˆ ๋‚˜ํƒ€๋‚  ์ˆ˜๋„ ์žˆ๋‹ค

    • ํ—ค๋”์˜ ์›€์ง์ž„์€ ์ผ๋ฐ˜ ๊ณผ์ œ์™€ ๋™์ผํ•˜๊ฒŒ CGAffineTransform ์™€ .identity๋ฅผ ์‚ฌ์šฉ

    • ์›€์ง์ด๋Š” ์กฐ๊ฑด์€ scrollView y์ขŒํ‘œ์˜ contentOffset์ด 0๋ณด๋‹ค ํฌ๋ฉด ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ž‘๋™ํ•˜๋„๋ก ํ–ˆ๋‹ค

      โ†’ ์ƒ๋‹จ์—์„œ ์Šคํฌ๋กคํ•˜๋ฉด ์›€์ง์ด์ง€ ์•Š๋„๋ก, ๊ทธ ์™ธ์˜ ๋ถ€๋ถ„์—์„œ๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ž‘๋™

    • isTop์ด๋ผ๋Š” boolean๊ฐ’์„ ์‚ฌ์šฉํ•ด์„œ headerView๊ฐ€ ์ƒ๋‹จ์—์„œ ์Šคํฌ๋กคํ•  ๋•Œ ์›€์ง์ด์ง€ ์•Š๋„๋ก ํ–ˆ๋‹ค


    ๐Ÿ”ฅ headerView์™€ scrollView๋ฅผ ๋‹ค๋ฅธ ์กฐ๊ฑด์— ๋„ฃ์€ ์ด์œ 

    โ†’ ๊ฐ™์€ ์กฐ๊ฑด(scrollView.contentOffset.y > 0)์— ๋„ฃ์—ˆ๋”๋‹ˆ ์ƒ๋‹จ์—์„œ ์Šคํฌ๋กค ํ•˜๊ณ  ๋‚˜์„œ ๋‘ View ์‚ฌ์ด์— โญ๏ธ๋นˆ ํ‹ˆโญ๏ธ์ด ์ƒ๊น€

    ๐Ÿ”ฅ self.scrollView.frame.size.height = UIScreen.main.bounds.size.height ์„ ๋งค๋ฒˆ ํ•ด์ฃผ๋Š” ์ด์œ 

    โ†’ scrollView์˜ frame์€ ์• ๋‹ˆ๋ฉ”์ด์…˜์— ์˜ํ•ด์„œ translationX์˜ y๊ฐ’์ •๋„ ์œ„๋กœ ์˜ฌ๋ผ๊ฐ€ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ๋Š” tabBar์™€ scrollView์‚ฌ์ด์— โญ๏ธํ‹ˆโญ๏ธ์„ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ํ‹ˆ์„ ๋ฉ”์šฐ๊ธฐ ์œ„ํ•ด์„  scrollView์˜ frame height๋ฅผ UIScreen์˜ height ์‚ฌ์ด์ฆˆ๋กœ ์ง€์ •ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

    ๐Ÿ“Œ ํ•˜์ง€๋งŒ ๋ฉˆ์ท„์„ ๋•Œ๋Š” ์›๋ž˜์˜ height๋กœ ๋Œ๋ ค์ค˜์•ผ ํ•œ๋‹ค๋Š” ๊ฑธ ์žŠ์–ด์„  ์•ˆ๋œ๋‹คโ—๏ธ

    โญ๏ธโญ๏ธ ๋ฌด์—‡๋ณด๋‹ค๋„ viewDidLoad์— scrollView์™€ TextField๋ฅผ Delegateํ•ด์ค˜์•ผ ํ•œ๋‹ค๋Š” ๊ฑธ ์žŠ์ง€ ๋ง์ž (์ œ๋ฐœ!!) โญ๏ธโญ๏ธ




๐Ÿฅ 3์ฃผ์ฐจ ๊ณผ์ œ (11/06_์ œ์ถœ ์™„๋ฃŒ) ๐Ÿฅ

โœจMain.storyboard์— ์ง  View์˜ ๋ชจ์Šต โœจ

  • ๊ณ ์ • View๋Š” 2์ฃผ์ฐจ ๊ณผ์ œ์ฒ˜๋Ÿผ ScrollView์™€ ๋ถ„๋ฆฌํ•ด์„œ ๋”ฐ๋กœ View๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค. ๊ทธ๋Ÿผ ์Šคํฌ๋กคํ•  ๋•Œ ํ•จ๊ป˜ ์˜ฌ๋ผ๊ฐ€์ง€ ์•Š๋Š”๋‹ค.

  • ์•„๋ฌด๊ฒƒ๋„ ๋ชจ๋ฅด๊ณ  CollectionView๋ฅผ ์‚ฌ์šฉํ•ด์„œ banner์™€ profile ํ™”๋ฉด์„ ๋ชจ๋‘ ๋งŒ๋“ค๋ ค๊ณ  ํ–ˆ์ง€๋งŒ ์‹คํŒจํ–ˆ๋‹ค๐ŸŒ

  • ๋จผ์ € ScrollView๋ฅผ ๋งŒ๋“ค๊ณ  ๊ทธ ์œ„์— ImageView์™€ CollectionView๋ฅผ ์–น๊ธฐ๋กœ ๋งˆ์Œ ๋จน์€ ๋’ค์— ์‹œ๋„โ—๏ธ

  • ๊ฒฐ๊ณผ์ ์œผ๋กœ ์œ„์— ํ™”๋ฉด์ฒ˜๋Ÿผ ๋‚˜์™”๋‹ค ์ง ๐ŸŽ†

    โ†’ ์ € ์ƒํƒœ๋กœ ์‹คํ–‰์„ ํ•˜๋ฉด CollectionView๋„ ์Šคํฌ๋กค๋˜๊ณ  ScrollView๋„ ์Šคํฌ๋กค๋˜๋Š” ์‹ ๋ช…๋‚˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜๊ธฐ์— ๐ŸŒŸ๊ผญ CollectionView์—์„œ View>Interaction>Multiple Touch ๋ˆŒ๋Ÿฌ์ฃผ๊ธฐ ๐ŸŒŸ


โœ…ProfileViewCell

func setCell(info: Profile) {
        profileImage.image = UIImage(named: info.imageName)
        profileName.text = info.imageName
        profileLabel.text = info.statusMessage
    }
  • cell์— setCell์ด๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด๋‘๊ณ  Profile Data๋ฅผ ๋ฐ›์•„์„œ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, ํ”„๋กœํ•„ ์ด๋ฆ„, ํ”„๋กœํ•„ ์ƒํƒœ ๋ผ๋ฒจ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.

โœ…ViewController(UICollectionViewDataSource)

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return profile.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let cell = profileCollectionView.dequeueReusableCell(withReuseIdentifier: ProfileViewCell.identifier, for: indexPath) as? ProfileViewCell else {
            return UICollectionViewCell()
        }
        cell.setCell(info: profile[indexPath.item])
				// ProfileViewCell ์•ˆ์„ ์„ค์ •ํ•˜๊ธฐ        

        return cell
    }
}
  • cell์ด๋ผ๋Š” ๋ณ€์ˆ˜์— ProfileViewCell๋ฅผ ๋ฐ›์•„์™€์„œ setCellํ•จ์ˆ˜๋ฅผ ๋ถ€๋ฅธ๋‹ค.
  • profile์ด๋ผ๋Š” ๋ฐ์ดํ„ฐ์˜ ํ•ด๋‹น indexPath์˜ item๋ฅผ ๋ฐ›์•„์˜จ๋‹ค โ†’ TableView๋Š” indexPath์˜ row โ—๏ธโ—๏ธ

โœ…ViewController(UICollectionViewDelegateFlowLayout)

extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 150, height: 225)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 0
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 27
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: 41, left: 20, bottom: 0, right: 20)
    }
}
  • TableView์™€๋Š” ๋‹ค๋ฅด๊ฒŒ CollectionView๋Š” ํ•ด๋‹น ์…€๋“ค์˜ ์œ„์น˜๋ฅผ FlowLayout๋ฅผ ํ†ตํ•ด์„œ ์žก์•„์ค„ ์ˆ˜ ์žˆ๋‹ค.
  1. ์ฒซ ๋ฒˆ์งธ ํ•จ์ˆ˜(sizeForItemAt) : ํ•ด๋‹น cell์˜ ํฌ๊ธฐ ์žก๊ธฐ
  2. ๋‘ ๋ฒˆ์งธ ํ•จ์ˆ˜(minimumLineSpacingForSectionAt) : ๊ฐ๊ฐ ์…€๋“ค ์œ„,์•„๋ž˜์˜ Space
  3. ์„ธ ๋ฒˆ์งธ ํ•จ์ˆ˜(minimumInteritemSpacingForSectionAt) : ๊ฐ๊ฐ ์…€๋“ค ์ขŒ,์šฐ์˜ Space
  4. ๋„ค ๋ฒˆ์งธ ํ•จ์ˆ˜(insetForSectionAt) : CollectionView ContentInset ์ง€์ •



๐Ÿฃ 2์ฃผ์ฐจ ๊ณผ์ œ (10/20_์ œ์ถœ ์™„๋ฃŒ) ๐Ÿฃ

  • ๋„์ „ ๊ณผ์ œ

    โœ…ViewController

    class ViewController: UIViewController, UIScrollViewDelegate {
        @IBOutlet weak var scrollView: UIScrollView!
        @IBOutlet weak var topButton: UIButton!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            scrollView.delegate = self
        }
    
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            if(scrollView.contentOffset.y > (scrollView.contentSize.height - scrollView.frame.size.height) / 2) {
                topButton.isHidden = false
            } else {
                topButton.isHidden = true
            }
        }
    }
    • scrollView๋ฅผ scrollViewDidScroll์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ UIScrollViewDelegate๋ฅผ Delegateํ•œ๋‹ค.
    • ์‚ฌ์šฉํ•  scrollView์— ์ฒ˜์Œ view๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ delegateํ•œ๋‹ค.
    • scrollView๊ฐ€ ์›€์ง์ผ ๋•Œ content offset์ด ๋ณ€๊ฒฝ๋˜๋Š” ๋ฐ ๊ทธ offset์˜ y๋ถ€๋ถ„์€ scrollView.bounds.origin.y์™€ ๊ฐ™๋‹ค.
    • ํ•ด๋‹น content offset์ด ์ „์ฒด ์Šคํฌ๋กค๋ทฐ์˜ ๋ฐ˜ ์ด์ƒ์œผ๋กœ ๋‚ด๋ ค์˜ค๋ฉด topButton์ด ๋ณด์—ฌ์ง€๊ณ  ์•„๋‹ˆ๋ฉด ๋ณด์ด์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค.

    @IBAction func touchUpTop(_ sender: Any) {
            topButton.isHidden = true
            scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
    }
    • topButton๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋ณด์ด์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค. top ๋ถ€๋ถ„์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ๋Š” ๋ฒ„ํŠผ์ด ๋ณด์ด์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
    • setContentOffset์€ UIScrollView์—์„œ ํŠน์ • ์œ„์น˜๋กœ scrollํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.
    • x์ถ•์œผ๋กœ๋Š” ์Šคํฌ๋กคํ•  ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์— 0์œผ๋กœ ํ•ด์ฃผ๊ณ  ๋งจ ์œ„๋กœ ์ด๋™ํ•  ๊ฒƒ์ด๊ธฐ์— y์ถ•๋„ 0์œผ๋กœ ๋งž์ถฐ์ค€๋‹ค.



๐Ÿฃ 1์ฃผ์ฐจ ๊ณผ์ œ (10/16_์ œ์ถœ ์™„๋ฃŒ) ๐Ÿฃ

  • ๋„์ „ ๊ณผ์ œ

    โœ…SecondViewController

    @IBAction func touchUpDismiss(_ sender: Any) {
            guard let dvc = self.presentingViewController as? ViewController else { return }
            dvc.editLabels(part: partTextField.text ?? " ", name: nameTextField.text ?? " ")
            dismiss(animated: true, completion: nil)
    }

    โœ…ViewController

    func editLabels(part: String, name: String) {
            partLabel.text = part
            statusLabel.text = "\(name) ๋‹˜ ์•ˆ๋…•ํ•˜์„ธ์š” ~~ ๐Ÿฅฐ"
    }
    • SecondViewController์—์„œ ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋Š” ๊ฒฝ์šฐ, ViewController๊ฐ€ presenting ๋˜๋ฉด์„œ ViewController์˜ editLabels๋ผ๋Š” ํ•จ์ˆ˜๋ฅผ call ํ•œ๋‹ค.
    • TextField.text์˜ ๊ฐ’์ด editLabels์˜ part์™€ name์œผ๋กœ ์ „๋‹ฌ๋œ๋‹ค.
    • ViewController์˜ partLabel.text๊ฐ’๊ณผ statusLabel.text๋กœ ๋„ฃ์–ด์ ธ์„œ ViewController๋กœ dismiss ๋์„ ๋•Œ label์˜ ๊ฐ’์ด ๋ชจ๋‘ ๋ณ€๊ฒฝ๋œ๋‹ค.

  • ๋„์ „ ๊ณผ์ œ(SnapKit ์‚ฌ์šฉ)

    โœ…ViewController

    @objc func touchUpPresent() {
            let loginVc = LoginViewController()
            let vc = UINavigationController(rootViewController: loginVc)
            vc.modalPresentationStyle = .fullScreen
            loginVc.editLabelText = { part, name in
                self.partNameLabel.text = part
                self.introduceLabel.text = "\(name) ๋‹˜ ์•ˆ๋…•ํ•˜์„ธ์š”!!!๐Ÿ˜‚"
            }
            present(vc, animated: true, completion: nil)
    }

    โœ…LoginViewController

    var editLabelText: ((String, String) -> ())?
    
    @objc func touchUpLogin() {
            editLabelText?(partTextField.text ?? " ", nameTextField.text ?? " ")
            dismiss(animated: true, completion: nil)
    }
    • ViewController์—์„œ ๋ฐ”๋กœ LoginViewController๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด NavigationController๊ฐ€ present๋˜์ง€ ์•Š๊ณ  NavigationController๋งŒ presentํ•˜๋ฉด ๋นˆ NavigationController๊ฐ€ present๋˜๊ธฐ ๋•Œ๋ฌธ์— UINavigationController์˜ rootViewController๋ฅผ LoginViewController๋กœ ์ •ํ•ด์ฃผ์–ด LoginViewController๊ฐ€ present๋  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜์˜€๋‹ค.
    • LoginViewController์—์„œ editLabelText๋ผ๋Š” closure๋ฅผ ์„ ์–ธํ•ด์„œ ViewController๋กœ TextField.Text๊ฐ’์„ ์ „๋‹ฌํ•ด์คฌ๋‹ค.

About

๐ŸŽ SOPT27 iOS ์‹ ์œค์•„ ๐ŸŽ

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published