ππ»ββοΈππ»ββοΈ λ¬λμ€νμ΄ iOS "μ§μ¬" λ ν¬ ππ»ββοΈππ»ββοΈ
- μ§νκΈ°κ° : 2020λ 6μ 27μΌ ~ 7μ 18μΌ 26th βSOPTβ APPJAM
- μ½λ 리ν©ν λ§ κΈ°κ° : 2021λ 3μ 25μΌ ~
μ°λ¦¬λ 'ν¨κ»' λΈ λ λ λ§μ μλμ§λ₯Ό λ§λ€μ΄λ λλ€. μμ¦ μΈλλ€μ λ¬λ ν¬λ£¨μ μμλμ΄ λ¬λμ μ¦κΈ°κ³€ νμ§λ§, Offline λ¬λν¬λ£¨λ κ³ μ λ μκ°κ³Ό μ₯μλΌλ νκ³μ μ κ°μ§κ³ μμ΅λλ€. μ°λ¦¬λ κ°μ΄ λ¬λ¦¬κ³ μΆμ§λ§, λ¬λ¦΄ μ μλ μκ°λ€μ΄ μ‘΄μ¬νλ€λ μ μ μ§μ€νκ³ μ ν©λλ€. μ°λ¦¬μ μλΉμ€λ₯Ό ν΅ν΄ μ곡κ°μ μ μ½μμ΄ λ€μν μ¬λλ€κ³Ό ν¨κ» λ¬λ¦ΌμΌλ‘μ¨ κ±΄κ°ν λΌμ΄ν μ€νμΌμ λ§λ€μ΄λκ°κΈΈ λ°λλλ€.
: κΈ°μ‘΄ λ¬λ μ΄ν리μΌμ΄μ κ³Όμ νμ€ν μ°¨λ³ν μμλ₯Ό μ μ©νμ΅λλ€.
: μ΄λ€ λ¨μ²΄μ μμλμ§ μμλ, κ΅³μ΄ μΉκ΅¬μ μ½μμ μ‘κ³ λ§λμ§ μμλ, μ¬μ©μλ μΈμ μ΄λμλ λμ ν¨κ» λΈ μλλ₯Ό μ°Ύμ κ°μ΄ λ¬λμ μ¦κΈΈ μ μμ΅λλ€.
βοΈ νΈλ λν λμμΈμ μμλ₯Ό ν΅ν΄ μκ°μ μΈ μ¦κ±°μμ μ»μ μ μμ΅λλ€.
: νΉμ λͺ©νλ₯Ό λ¬μ±νλ©΄ κ°μ±μλ μ¬λ¬ κ°μ§ λ±μ§λ₯Ό νλν μ μμκ³Ό λμμ λ€μν λ¬λ λνΉμ ν΅ν΄ μ¬μ©μμ μ¦κ±°μμ λλͺ¨νκ³ μ ν©λλ€. λ, λꡬλ μ¬μ©νκΈ° μ¬μ΄ UX μμλ μ¦κ±°μμ λ°°λ‘ ν₯μμμΌ μ€κ±°μμ.
μ°μ μμ | νλ©΄ μ΄λ¦ | κΈ°λ₯ μ΄λ¦ | ꡬν | λ΄λΉμ |
---|---|---|---|---|
P1 | νμ κ°μ | κΈ°λ³Έ λ°μ΄ν°(λλ€μ/μμ΄λ/λΉλ°λ²νΈ/μ±λ³/λ 벨) λ°κΈ° | π΅ | μ΅μμ¬ |
P2 | νν λ¦¬μΌ | μ€νλμ¬ | π΅ | κΉλ―Όν¬ |
P1 | 맀μΉνλ©΄ | λ§€μΉ λ°μ΄ν°(λͺ©νμκ°/μλ μ±λ³)λ°κΈ° | π΅ | κΉλ―Όν¬ |
P1 | 맀μΉνλ©΄ | λ§€μΉ κΈ°μ€(λͺ©νμκ°/μ±λ³/λ 벨)μ λ°λΌ 맀μΉλ¬λ μ°ΎκΈ° | π΅ | κΉλ―Όν¬ |
P1 | 맀μΉμ±κ³΅νλ©΄ | μλλ¬λ νλ‘ν, λ 벨, μ μ 보μ¬μ£ΌκΈ° | π΅ | κΉλ―Όν¬ |
P1 | λ‘κ·ΈμΈ | λ‘κ·ΈμΈ κΈ°λ₯ | π΅ | μ΅μμ¬ |
P1 | λ¬λμ€νλ©΄ | μ€μκ°μΌλ‘ λ¬λ거리, νμ΄μ€, λ¨μ μκ° λ³΄μ¬μ£ΌκΈ° | π΅ | κΉλ―Όν¬ |
P1 | λ¬λμ€νλ©΄ | μΉ΄μ΄νΈλ€μ΄ | π΅ | κΉλ―Όν¬ |
P1 | λ¬λμ€νλ©΄ | ν΄λν° μ¬μ©μ λμ λ° νΌνΈλμ€ νλ μ κ·Ό | π΅ | κΉλ―Όν¬ |
P2 | λ¬λμ€νλ©΄ | νμ¬ μμΉ νμ | π΅ | κΉλ―Όν¬ |
P2 | λ¬λλνΉνλ©΄ | 1) μ΄λ거리 λμμ 2)μΉμ΄ λ§μ μ 3)ν¨κ° λ§μ μ μ¬μ©μ λνΉ | π΅ | μ΅μμ¬ |
P2 | λ¬λκΈ°λ‘νλ©΄ | λμ λ¬λλ°μ΄ν° λͺ¨μμ 보μ¬μ£ΌκΈ° κΈ°λ₯ | π΅ | μ΅μμ¬ |
P2 | λ¬λκΈ°λ‘νλ©΄ | λ¬λ λ±μ§ λͺ¨μμ 보μ¬μ£ΌκΈ° κΈ°λ₯ | π΅ | μ΅μμ¬ |
P2 | λ¬λκ²°κ³Όνλ©΄ | λ λ¬λμ μ΄λ거리 λΉκ΅ κΈ°λ₯ | π΅ | κΉλ―Όν¬ |
P1 | λ¬λκ²°κ³Όνλ©΄ | λμ κΈ°λ‘/μλλ¬λ κΈ°λ‘ λͺ¨μμ 보μ¬μ£ΌκΈ° κΈ°λ₯ | π΅ | κΉλ―Όν¬ |
P2 | λ§μ΄νμ΄μ§ | λμ νλ‘ν μ΄λ―Έμ§/λλ€μ/μΉν¨/λ 벨/λ±μ§ νμ | π΅ | μ΅μμ¬ |
: Tab Barμ μ΄μ©ν΄ κ° μ€ν 리보λλ₯Ό μ΄μ΄μ£Όλ©° μ΄λ κ°λ₯
π₯³ λ‘κ·ΈμΈ, νμκ°μ
νλ©΄. μ κ·ννμ κ²μ¬μ μμ΄λ, λλ€μ μ€λ³΅ κ²μ¬ μλλ‘κ·ΈμΈ κΈ°λ₯μ ν¬ν¨νλ€.
π₯³ Main νλ©΄. μΌλ¬μ€νΈλ‘€ ν¬ν¨νλ©°, RUN NOW λ²νΌ ν΄λ¦μ μ£Όμ κΈ°λ₯μ λ§€μΉ μλΉμ€κ° μμλλ€. μνλ λ§€μΉ λ¬λμ 쑰건μ μ€μ νκ³ λ§€μΉμ΄ λλ©΄ μλλ°© νλ‘νμ νμΈν ν, λ¬λμ΄ μμλλ€.
π€ μΆν developμΌλ‘ νΌμ λ¬λ¦¬κΈ°λ₯Ό ν΅ν΄ 맀μΉμ΄ μλλ μ¬λλ€ νΉμ νΌμ λ¬λμ μΈ‘μ ν΄λ³΄κ³ μΆμ μ¬λλ€μ μν μλΉμ€λ₯Ό μ 곡νκ³ μ νλ€.
π₯³ 맀μΉμ΄ λλ©΄ μλλ°© νλ‘νμ νμΈν ν, λ¬λμ΄ μμλλ€. μ€μκ°μΌλ‘ μμ μ μλ(km), νμ΄μ€(pace), λ¨μ μκ°μ΄ νμλκ³ νμ¬ μμ μ΄ λ°κ³ μλ μμΉκ° μ§λ(λ€μ΄λ²)λ₯Ό ν΅ν΄ νμλλ€.
π€ νμ¬λ μμ μ΄ μλ μμΉλ§ νμλμ§λ§, μΆν μμ μ΄ λ¬λν κ²½λ‘κ° λ¨κ² ν μκ°μ΄λ€. (νμ¬ λ€μ΄λ²μΈ‘μμ points μμ±μ λ§μλκ²μΌλ‘ λ³΄μ¬ κ΅¬ννμ§ λͺ»ν μμ¬μμ΄ μλ€.)λ€μ΄λ² μ§λ iOS SDK
: μμ μ΄ μ§κΈκΉμ§ μμμ¨ λ¬λ κΈ°λ‘κ³Ό λͺ¨μμ¨ λ¬λλ±μ§λ₯Ό νμΈν μ μλ€.
: νμ¬ μ΄ μ΄νμμ 'μ΄λ¬μ λ¬λ', 'λͺ μμ μ λΉ', 'μ‘.μ.μΈ' λ‘ λλ μ§ 3κ°μ λνΉ μμλ₯Ό νμΈν μ μλ€.
: μμ μ κΈ°λ³Έ μ 보(νλ‘ν μ¬μ§, λλ€μ, λ 벨, μ μ )μ νμΈν μ μκ³ λͺ¨μλ λ±μ§λ₯Ό κ°λ΅ν νμΈν μ μλ€.
πͺπ»μ΄λ μ΄νμ κΈ°λ³Έ! '건κ°' μ΄ν리μΌμ΄μ μ°λν΄μ κ° κ°μ Έμ€κΈ° ! λμ !πͺπ»
- λ¨Όμ κ±΄κ° μ΄ν리μΌμ΄μ μ μ°λνλ €λ©΄ Apple Developer Program Membership μ΄ νμν©λλ€ (μ λ£)
- Capabilityμμ Healthkitλ₯Ό μΆκ°ν΄ νμ±ν μμΌμ€λλ€.
- info.plist νμΌμμ Information Property List Key Referenceλ₯Ό μΆκ°ν΄μ€λλ€.
- HealthKit μ‘μΈμ€λ₯Ό νμ©νλ μ½λλ₯Ό μ§μΌν©λλ€.
- μ΄ κΈ°κΈ°μμ HealthKitλ₯Ό μ¬μ©ν μ μλμ§ νμΈνκΈ°.
guard HKHealthStore.isHealthDataAvailable() else {
completion(false, HealthkitSetupError.notAvailableOnDevice)
return
}
- HealthKitκ³Ό μνΈ μμ©ν λ°μ΄ν° μ ν μ€λΉνκΈ°.
guard let walkAddRun = HKObjectType.quantityType(forIdentifier: .distanceWalkingRunning),
//κ±·κΈ°+λ¬λ¦¬κΈ° 거리(walkAddRun)
let step = HKObjectType.quantityType(forIdentifier: .stepCount),
// μ¬λ°μ
let activeEnergy = HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)
//νλμλμ§
else {
let activeEnergy = HKObjectType.quantityType(forIdentifier: .activeEnergyBurned) else {
completion(false, HealthkitSetupError.dataTypeNotAvailable)
return
}
(λ°μ΄ν° μ νμ https://developer.apple.com/documentation/healthkit/hkquantitytypeidentifier μμ μ°Έκ³ ν μ μμ΅λλ€.)
- HealthKitμ΄ μ½κ³ μΈ μ ν λͺ©λ‘ μμ±νκΈ°
let healthKitTypesToWrite: Set<HKSampleType> = [walkAddRun,
activeEnergy,
step,
HKObjectType.workoutType()
]
let healthKitTypesToRead: Set<HKObjectType> = [walkAddRun,
activeEnergy,
HKObjectType.workoutType()
]
healthKitTypesToWriteλ μ΄νμ΄ λ€μ λ°μ΄ν°λ₯Ό μ°κΈ° νμ©νλ λͺ©λ‘, healthKitTypesToReadλ μ΄νμ΄ λ€μ λ°μ΄ν°λ₯Ό μ½κΈ° νμ©νλ λͺ©λ‘μ λλ€.
- μμ² μΉμΈ λ°κΈ°
HKHealthStore().requestAuthorization(toShare: healthKitTypesToWrite,
read: healthKitTypesToRead) { (success, error) in
completion(success, error)
}
import SocketIO
static let shared = SocketIOManager()
var manager = SocketManager(socketURL: URL(string: "μμΌμ£Όμ")!, config: [.log(true), .compress])
var socket: SocketIOClient!
FindRunnerVC.socket = FindRunnerVC.self.manager.socket(forNamespace: "/matching")
: μ΄νμμ μμΌ ν΅μ μ μ°λ λ²μλ λ§€μΉ λ¬λλ μ°Ύμ λλΆν° κ²μμ΄ λλκ³ κ²°κ³Ό λΉκ΅ μ κΉμ§ μμΌ ν΅μ μ μ¬μ©νμ΅λλ€.
ν λ·°μμλ§ μμΌ ν΅μ μ΄ μ°μ΄λ κ²μ΄ μλλΌ μμΌμ΄ μ°κ²°λΌμ μ¬λ¬ λ·°λ₯Ό ν΅κ³ΌνκΈ° λλ¬Έμ μ±κΈν€μ μ¬μ©ν΄μ ν΅μ μ μ΄μ΄κ°μ΅λλ€.
FindRunnerVC.socket.on("start", callback: { (data, ack) in
FindRunnerVC.self.socket.emit("joinRoom",myToken,myGoal,myWantGender,self.leftTime)
})
: μλ²λ μμν΄λ μ’λ€λ μλ΅ -> ν΄λΌλ λ΄ μ 보μ λ΄κ° μνλ μλμ 쑰건μ 보λ΄μ€λλ€.
FindRunnerVC.socket.on("roomCreated", callback: { (data, ack) in
FindRunnerVC.self.socket.emit("startCount",data[0] as! SocketData)
})
: λ΄κ° μνλ 쑰건μ μλλ₯Ό μ°Ύμ§ λͺ»νλ€λ©΄ μλ²λ μλ‘μ΄ λ°μ λ§λ€μ΄μ λλ₯Ό λ£μ΄μ£Όκ³ -> ν΄λΌλ μκ° μΉ΄μ΄νΈλ₯Ό νλΌκ³ μλ €μ€λλ€. roomCreated λ λ°λ μ λ³΄μΈ data[0]μ λ΄κ° μν΄μλ λ°©μ λ²νΈκ° λ©λλ€.
FindRunnerVC.socket.on("matched", callback: { (data, ack) in
FindRunnerVC.self.socket.emit("endCount",data[0] as! SocketData)
})
: λκΈ°μ€μ μλλ₯Ό μ°Ύμμλλ μλ²λ matchedμ data[0]μΌλ‘ λ΄κ° μν λ°© μ΄λ¦μ 보λ΄μ£Όκ³ , ν΄λΌμμλ μ¬μ©μκ° μν λ°©μ μ΄λ¦κ³Ό endCountλ₯Ό 보λ΄μ€λλ€.
FindRunnerVC.socket.emit("compareResult",UserDefaults.standard.object(forKey: "opponentRoom") as? String ?? " ",UserDefaults.standard.object(forKey: "opponetDistance") as? Int ?? 2,UserDefaults.standard.object(forKey: "myGoalTime") as? Int ?? 0,array2,UserDefaults.standard.object(forKey: "createdTime") as? String ?? " ",UserDefaults.standard.object(forKey: "endTime") as? String ?? " ")
: λ¬λ κ²μμ΄ λλλ©΄ λμ λ°© λ²νΈ, λ¬λ¦° 거리, λ¬λ¦° μκ°, λ°© λ²νΈ, μ§λ μμΉ, μμν μκ°, λλ μκ°μ ν¨κ» 보λ΄μ€λλ€.
- μ κ·ννμκ³Ό μ€μκ° μ λ ₯ 체ν¬
private func loginCheck() {
idTextField.addTarget(self, action: #selector(idTextChecked(_:)), for: .editingChanged)
nickTextField.addTarget(self, action: #selector(nickTextChecked(_:)), for: .editingChanged)
pwTextField.addTarget(self, action: #selector(pwTextChecked(_:)), for: .editingChanged)
pwReTextField.addTarget(self, action: #selector(pwReTextChecked(_:)), for: .editingChanged)
}
editingChangedλ₯Ό νμ©ν ν¨μλ₯Ό μ μΈν΄μ£Όμ΄ μ€μκ°μΌλ‘ 쑰건μ λ§λ μ λ ₯μ΄ λ€μ΄μ€λμ§ μ²΄ν¬νκ³ , κ·Έμ λ°λ₯Έ ν μ€νΈλ₯Ό λμ°κ±°λ, 쑰건μ λ§μμΌ λ€λ₯Έ λμλ€μ μ€νν μ μλλ‘ νλ€.
func isValidNick(nick: String?) -> Bool {
guard nick != nil else { return false }
let regEx = "[γ±-γ
γ
-γ
£κ°-ν£A-Za-z0-9]{2,6}"
let pred = NSPredicate(format:"SELF MATCHES %@", regEx)
return pred.evaluate(with: nick)
}
λλ€μ μ κ·νν 쑰건
ν μ€νΈ νλκ° μ‘°κ±΄μ λ§μ§ μλλ€λ©΄, μ€λ₯ λ©μΈμ§μ ν¨κ² ν μ€νΈ νλμ λμμΈμ λ³κ²½ν΄μ£Όκ³ λ€μμΌλ‘ λμ΄κ° μ μλ λ²νΌμ λΉνμ±ν μν€λ λ±μ ꡬνμ νλ€. μ κ·ννμ μ체보λ€λ μ€μκ°μΌλ‘ μ¬μ©μμ μ λ ₯μ νμΈνμ¬ μ‘°κ±΄μ κ²μ¬ν΄μ£Όλ λΆλΆμ΄ μ΄λ €μ λλ°, μ°λ¦¬ νμ κ²½μ°μλ νμκ°μ μμ 체ν¬ν΄μΌνλ μ‘°κ±΄μ΄ λ€μνκΈ° λλ¬Έμ λ·°λ₯Ό μ§λ κ³Όμ μ΄ κΉλ€λ‘κ³ μκ°μ΄ μ€λκ±Έλ Έλ€.
-
컬λ μ λ·°μ IsSelectedλ₯Ό νμ©ν μ νμ§ κ΅¬ν
override var isSelected: Bool { willSet { self.genderActionLabel.backgroundColor = newValue ? UIColor.lightishBlue : UIColor.brownishGrey print(genderActionLabel.text ?? nil!)
νμκ°μ μμ μ¬μ©μμ μΆκ° μ 보λ₯Ό μ λ ₯ν΄μΌνλ λΆλΆμ μ²μμλ λ¨μνκ² λ²νΌμΌλ‘ ꡬννλ €κ³ νμλ€. κ·Έλ¬λ μ¬μ©μκ° νλμ μ νμ§λ§μ μ νν΄μΌνκ³ , κ·Έ κ°λ§μ μλ²μ μ λ¬ν΄μΌνλ 쑰건 νμ μΌλ° λ²νΌμΌλ‘λ κΈ°νμ ꡬνν μκ° μμλ€. κ·Έλ κΈ°μ 컬λ μ λ·°μμ IsSelectedλ₯Ό νμ©νμ¬ μ¬μ©μκ° νλλ§μ μ νμ§λ₯Ό κ³ λ₯Ό μ μκ² κ΅¬ννμλ€. μλλ‘μ΄λμμλ κΈ°λ³Έμ μΌλ‘ μ 곡λλ κΈ°λ₯μ΄μ§λ§, iOSμμλ μΌμΌμ΄ ꡬνν΄μ£Όμ΄μΌνλ κΈ°λ₯μ΄μλ€. μ ν μμ§ λͺ»νλ κΈ°λ₯μ΄μκΈ°μ λ§μ μ¬λλ€μ λμμ λ°μκ³ , ꡬκΈλ§κ³Ό μμ μ νλλ°μ λ§μ μκ°μ΄ λ€μλ€.
- CoreMotionμ ν΄λν°μ μΌμλ₯Ό μ¬μ©νλ λ°©λ²μ΄λ€. μλ Healthkitλ₯Ό μ¬μ©νμ§λ§, Healthkitλ λ§μ μ 보λ₯Ό μ 곡ν΄μ£Όμ§λ§ μ
λ°μ΄νΈκ° μ€μκ°(λͺ μ΄ λ¨μ)λ‘ λμ§μλ λ¬Έμ κ° λ°μν΄μ CoreMotionμΌλ‘ μμ νμλ€.coremotionμ μ΄μ©ν΄μ μ€μκ° κ±°λ¦¬λ₯Ό λ°μμλ€. λ¬Όλ‘ μ¬κΈ°μλ νμ΄μ€λ₯Ό κ³μ°ν΄μ£Όμ§λ§ μ°λ¦¬κ° μνλ λ¨μμλ λ¬λκΈ°λλ¬Έμ νμ΄μ€λ μ§μ κ³μ°νλ λ°©λ²μ μ¬μ©νλ€.
CoreMotion
pedometer = CMPedometer()
: CMPedometerλ μ€μκ° κ±·κΈ°λ₯Ό κ°μ Έμ€κΈ° μν κ°μ²΄μ΄λ€. μ¦ λ§λ³΄κΈ°μ μν μ νλ€. λ§λ³΄κΈ°λ₯Ό ν΅ν΄ κ±Έμ μ λ° μ΄λ ν 거리μ μμΉ λλ νκ° ν μΈ΅ μμ λν κΈ°ν μ 보λ₯Ό κ²μν μ μλ€.
pedometer.startUpdates(from: Date(), withHandler: {
(pedometerData, error) in
if let pedData = pedometerData{
self.numberOfSteps = Int(pedData.numberOfSteps)
if let distance = pedData.distance{
self.distance = Double(distance)
}
if let averageActivePace = pedData.averageActivePace {
self.averagePace = Double(averageActivePace)
}
if let currentPace = pedData.currentPace {
self.pace = Double(currentPace)
}
} else {
self.numberOfSteps = nil
}
})
: startUpdates ν¨μλ₯Ό ν΅ν΄ 보νμ κ΄λ ¨ λ°μ΄ν°λ₯Ό μ±μ λκ³ μ¨λ€. distanceλ μ¬μ©μκ° κ±·κ±°λ λ°λ λμ μ΄λν 거리λ₯Ό κ°μ Έμμ€λ€. averageActivePaceλ λ―Έν°λΉ μ΄ λ¨μλ‘ νμ°ν νκ· νμ΄μ€μ΄λ€. currentPaceλ λ―Έν°λΉ μ΄ λ¨μλ‘ μΈ‘μ ν νμ¬ μλμ΄λ€. μ΄λ κ² CoreDataμμλ λ¬λ¦¬κΈ°μμ νμν λ§μ μ 보λ€μ μ 곡νλ€.
- κΉλ―Όν¬ Repo : λ¬λμ€νμ΄ iOS 리λκ°λ°μ, MainTab ꡬν λ° μμΌ ν΅μ λ΄λΉ
- μ΅μμ¬ Repo : λ¬λμ€νμ΄ iOS μλΈκ°λ°μ, RankTab / RecordTab / MyPabeTab / SignTab λ΄λΉ λ° http ν΅μ λ΄λΉ
: IT μ°½μ
λμ리 26th βSOPTβ APPJAM λμ μμ, νμ¬ μ±μ€ν μ΄ μΆμ μ€λΉ μ€.