model, view, viewmodel 이렇게 세가지로 나누어서 유지 보수 및 테스트에 적합하게 구성한 패턴을 mvvm이라 부른다

이 방식은 기존 uikit을 이용하는 swift에서 소개라 해야하나 권장해야한다해야하나 그런 cocoaMVC방식

- model, view, controller 세부분으로 나누던 mvc방식에서 View-Controller, model 두가지로 나누는 방식 

에서 swiftUI를 나오면서 뷰와 컨트롤러를 나누기 쉽게되어 많이 쓰이고있다

 

솔직히 무슨 패턴이니 하면서 하는걸 대부분 혼자 프로젝트 생성부터 배포까지 해왔던 나에겐 크게 다가오지 않는 방식이다

이런거 없이 개발해오던 내 느낌으로는 어떤 특정 스킬을 전체적인 규칙으로 특화해둔 느낌이라 뭔가 예의차리는 느낌이다

 

감상은 그만 말하고 다시 mvvm에 대해 이야기하자

ui 부분과 실 서비스 컨트롤부분 그리고 데이터 부분을 나눈방식이라 이해가 가는데 영 감이 안잡힌다 그래서 일단 해봤지만 잘 분리가 안됐을수도 있다.

 

아 참고로 이렇게 코드를 분리한 이유는 맨위에서 말했듯이 SwiftUI때문이다 이걸 공부하고싶은김에 이 프로젝트의 ui를 uikit에서 swiftui로 변경하고싶어 코드를 세부분으로 변경했다.

라곤 했지만 솔직히 기존 프로젝트에서 크게 다르지않다. 메인vc에서 뮤직플레이어로 서비스부분을 분할했다.

 

 

나눈 부분의 이야기를 하자

(일단 변명을 미리 하자면 내가 이해하기로는 model은 데이터구조, view는 ui코드, viewmodel은 비지니스코드로 이해했다)

model 은 다를 음악 파일의 데이터가 될 MusicInfo

view는 기존 NewMainVC, PlayListCell

viewmodel은 PlayList, MusicPlayer

이렇게 분리되었다.

뭐 추가된부분은 musicplayer부분만이지만

 

view부분에서 연결된 부분은

@objc func reloadButtonAction() {
    PlayList.shared.loadList()
    self.playListView.reloadData()
  }
  
  @objc func playButtonAction(){
    _ = player.play()
    setPlayIcon(isPlay: player.isPlaying)
  }
  
  @objc func preButtonAction() {
    _ = player.previousMusic()
    setPlayIcon(isPlay: player.isPlaying)
  }
  
  @objc func nextButtonAction() {
    _ = player.nextMusic()
    setPlayIcon(isPlay: player.isPlaying)
  }

기존 같이 있던 플레이어 기능을 전부 MusicPlayer.swift 파일로 빼서 이렇게 한단계 거치게됐다

참고로 찾아보니 테이블뷰에 Datasource 부분도 따로 빼서 연결해둔사람이 있었는데

cellForRowAt 부분에 셀의 ui변경코드를 넣어둬서 따로빼면 오히려 더 알아보기 힘들어질것같아서 냅뒀다

플레이어부분은 아래처럼 한번 감싸서 한번 생성되어 사용되도록해서 여러 부분에서 불러도 지장없이 해놨다

class MusicPlayer : NSObject {
  static var shared: MusicPlayer = .init()
  
  var avPlayer: AVAudioPlayer = .init()
  var playList: PlayList = .shared
  var paused: Bool = false
  var isPlaying: Bool {
    get {
      return self.avPlayer.isPlaying
    }
  }
  
  private override init() {
    super.init()
    initPlayer()
    remoteCommandCenterSetting()
  }
  
  private func initPlayer() {
    let audioSession = AVAudioSession.sharedInstance()
    do {
      /// 이유를 모르겠다 옵션즈에 값을 넣으면 락스크린에 컨트롤바가 안생김....
//      try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers])
      try audioSession.setCategory(.playback, mode: .default, options: [])
    } catch let error as NSError {
      print("audioSession 설정 오류 : \(error.localizedDescription)")
    }
  }
  
  ~~~

 

전체 부분을 보고싶으신분은 github을 봐달라.

https://github.com/wiwi-git/proj_ypl

 

GitHub - wiwi-git/proj_ypl: ios에서 간단하게 쓸 백그라운드 플레이어가 필요하다

ios에서 간단하게 쓸 백그라운드 플레이어가 필요하다. Contribute to wiwi-git/proj_ypl development by creating an account on GitHub.

github.com

 

이거로 3편에서 계획한 5단계에서 큐플레이어로 대체하는것을 제외하고 끝났다

막상하려니 고쳐야하는 부분이 많아보여서 그냥 이부분은 넘어가도록했다 기능적인 문제에 별거없었고...

 

다음은 swiftUI로 변경해보는김에 디자인을 좀 꾸며볼 생각이다

솔직히 너무 없어보여서;;;;

원래는 나 혼자 그냥 공부겸 만들거라 기능만 있으면 되겠지 싶었는데 이렇게 블로그에 글을쓰다보니 조금 부끄러워졌다

마침 디자인해주는 공짜ai서비스에 대해 듣기도했고 이걸 이용해 볼 생각이다

 

 

반응형

이전 시큐리티기능을 사용하지 않고 따로 히든텍스트라든 변수를 정의해줘서 비밀번호 가리고 보이고하는 기능을 구현해놨었다

해당 스크린을 테스트하기위해 테스트코드를 짜놨었는데....

 

이번 최신 버전을 배포하던중 텍스트필드에 중대한 버그가 발견되어 텍스트필드의 시큐리티 기능을 이용하는 방식으로 전환했다

그리고.. 그 이후로 UI테스트코드를 통과하지 못하고있다

도저히 잘못된점을 모르겠어서 뷰를 하나하나 스택쌓아가듯이 찾아 나아가보지만 여전히 해당 아이디를 가진 텍스트필드를 찾을 수 없다고 오류를 뱉고있었다.

 

나의 이 멍청한 시도를 다른이는 하지 않기를 기도하며 문제점을 여기에 적는다

나는 해당 부분을 이렇게 정의하며 찾아보고있었다.

- 라인2 오류 -

let loginvcContentView = app.otherElements["loginvcScrollViewContentView"]
let pwTextField = loginvcContentView.textFields["loginvcPwTextField"]

UITest에 대해 누가 알려주지 않아 거의 그냥 막코딩이라 변수이름이라던가 방법은 그냥 넘어가줬으면 좋겠다

문제는 loginvcContentView에서 찾는 엘레먼트 타입이였다.

텍스트필드에

textField.isSecureTextEntry = true

라고 정의를 해주면 textFields로는 찾을 수 없고

아래와 같이 해줘야한다.

let loginvcContentView = app.otherElements["loginvcScrollViewContentView"]
let pwTextField = loginvcContentView.secureTextFields["loginvcPwTextField"]

그렇다 

저 속성하나 켜줬다고 textField가 아닌 secureTextFields로만 찾을 수 있게되었다.

망할

 

반응형

'iOS > swift' 카테고리의 다른 글

No such module ~~ 설치되지 않는 Snapkit 문제  (0) 2023.04.12
ios - Admob 에러코드 메모  (0) 2023.03.21
Alamofire.AFError Code  (0) 2022.10.06
Block Based KVO, iOS - contentSize  (0) 2022.09.27
Date 끼리의 비교  (0) 2022.08.04

이번글은 6. 조건문과 반복문에서 언급한 enum에 대한 글이다.

직접 만드는 자료형중에 하나라고 소개하고싶다.

정수가 여러가지 수를 모아 둔 형태라 하면 이 enum 은 사용자가 원하는것들을 모아둘수있다.

6번글에서 switch 예제로 사용한 코드를 다시보면

    var a = "banana"

    switch a {

      case "banana" : print("banana")

      case "apple" : print("apple")

      case "melon" : print("melon")

      case "lemon" : print("lemon")

      case "peach" : print("peach")

      default: print("다른 종류이다.")

    }

출처: https://wiwi-pe.tistory.com/105?category=976417 [선생님 개발블로그가 하고싶어요.:티스토리]

라고 되어 있는데.

여기서 a는 String이라는 자료형을 가지게된다.

switch는 조건으로 들어온 a가 가질수 있는 모든 값에 대해 case를 지정해줘서 조건을 분기하는 기능을 하는데

default에서 이외의 String값을 처리해주고있는데 오타라던가가 발생하면 항상 default로 빠져버린다.

여기서 만약 a가 enum이였다면 훨씬더 깔끔한 모습으로 안정적이게 바꿀수있다.

a 값이 

banana apple melon lemon peach 중 하나라면 

enum Fruit {
    case banana
    case apple
    case melon
    case lemon
    case peach
}

var a = Fruit.banana
switch a {
    case .banana : print("banana")
    case .apple : print("apple")
    case .melon : print("melon")
    case .lemon : print("lemon")
    case .peach : print("peach")
}

위와 같은 형태로 a값에 대한 오류를 사전에 방지를 할 수 있게된다.

이 이외에도 조건문의 기준으로 사용하기위해 불형 변수를 하나 사용한다하면

var flag = false

if flag { print("참일때 실행해라" }

이 항목을 

enum Some {
    case run
    case stop
}
var flag = Some.run
if flag == .run { print("참일때 실행해라") }

이와 같이 변경해서 사용하면 가독성과 확장성이 좋아진다.

가독성이야 이름때문에 그렇다해도 확장성이 왜 좋아지냐면

만약 flag가 나중에 run또는 stop이 아닌 pause를 가지게 된다면 단순히 Some에서 case pause를 추가해주면 된다. 또다른 flag를 사용하여 연속으로 if문을 중첩하여 분기를 해주는것보다 훨씬 확장성이 좋다.

다시 위의 바나나에 관한 이야기로 돌아가자면 enum자체에도 하나의 특정한 값을 가지게 할수도 있고 함수를 가지게 할 수 도 있는게 스위프트의 장점인데 이에 대해 예제로 하나 퉁치며 이번 enum을 마치려고 한다.

enum Fruit : Int {
    case banana = 12
    case apple
    case melon
    case lemon
    case peach
    
    var stringValue: String {
        get {
            switch self {
            case .apple: return "apple"
            case .banana: return "banana"
            case .lemon: return "lemon"
            case .melon: return "melon"
            case .peach: return "peach"
            }
        }
    }
}


var a = Fruit.lemon
switch a {
    case .banana :
        print("banana")
    case .apple :
        print("apple")
    case .melon :
        print("melon")
    case .lemon :
        print("lemon")
    case .peach :
        print("peach")
}
print(a.rawValue, a.stringValue)

이 코드를 실행해보면

lemon

15 lemon 

와 같은 두줄이 뜨게 되는데 첫줄은 switch문에의한 print이고 두번째 줄은 switch 이후의 print문이다.

enum은 rawValue를 지정해줘서 가져올수 있는데 

rawValue를 위에 banana = 12 로만 주면 아래에 들어가는 값을 자동으로 순서대로 지정된다.

추가로 enum에는 stringValue처럼 값을 지정해주거나 func을 새로 넣어 줄 수 있어 조건문같은와 같이 쓰기 매우 좋은 형태이다.

 

- 변수에 대괄호를 열어 get을 정의해줬는데 이에 대해서는 앞 글에서 다루지 않아 이후에 또 다룰수 있으면 좋겠다.

- 함수에 대해서도 다룬적이없다.

반응형

Swfit의 compare를 이용하면 Date끼리의 비교도 가능하다.

이글에서 할 순서는

1. 동일 문자열 형식으로 비교할 값을 정의한다.

2. 동일한 DateFormatter로 String 을 Date 형으로 변경한다

3. 비교

비교할 형식은이다 "yyyy-MM-dd "

아래는 gist로 작성한 예제 코드이다.

 

compare는 Same, DESC, ASC로 세가지의 결과를 반환해주는데

이를 이용해 Date 끼리의 비교를 따로 함수로 정의해서 사용하거나 바로 이렇게 사용할수 있다.

반응형

https://stackoverflow.com/questions/18946302/uinavigationcontroller-interactive-pop-gesture-not-working

UINavigationController에 붙어있는 ViewController이지만백제스쳐가 동작하지 않는 경우가 왕왕있다.

 

해당 문제는 제스쳐 이벤트 끼리의 충돌문제인것으로 보인다이때 네비게이션 제스쳐를 하나의 UINavigationController 내부의 viewControllers개수가 하나보다 많을때만 동작하도록 지정해주는 걸 달아주면 이상하게도 동작을 잘한다.

 

해당 소스는 상단 스택오버플로우의 답에 달린 코드이다.

class NavigationController: UINavigationController, UIGestureRecognizerDelegate {

    /// Custom back buttons disable the interactive pop animation
    /// To enable it back we set the recognizer to `self`
    override func viewDidLoad() {
        super.viewDidLoad()
        interactivePopGestureRecognizer?.delegate = self
    }

    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        return viewControllers.count > 1
    }

}
반응형

pickerView는 label이나 button처럼 손쉽게 텍스트에 대한 접근을 할 수 없다.

생긴것부터가 배열이니 배열 전체에 대해 어떻게 해줄까를 정해줘야되서 그렇게 해둔듯 하다.

그러한 부분을 어떻게 할지를 정하는 함수가 pickerview에는 있다.

 

func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString?

 

라는 함수 인데

텍스트를 그냥 넣어 줄때 사용하는 

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?

와 사용이 비슷하다.

 

아래는 빨간색으로 변경하는 예시를 플레이그라운드에서 만들어 보았다.

텍스트 설정을 변경하는 키값이 상당히 많은데 그중에서도

foregroundColor

를 사용했다.

 

위 소스를 플레이그라운드에서 돌리면 아래와 같이 나온다.

 

아래는 키들에 대한 애플문서이다.

https://developer.apple.com/documentation/foundation/nsattributedstring/key

 

Apple Developer Documentation

 

developer.apple.com

 

반응형

'iOS > swift' 카테고리의 다른 글

KeyboardObserver~  (0) 2021.10.19
Guide Swift Programming Language 5.5  (0) 2021.10.01
프로그래머스 - 프린터  (0) 2021.05.06
프로그래머스 - 기능개발  (0) 2021.05.05
프로그래머스 - 모의고사  (0) 2021.05.04

기본연산자 목록에 안넣어버린 논리연산자다.

이전에 데이터 타입 불을 다룰때 잠깐 했던거 같기도하다.

 

논리 연산은

참과 거짓에 대해 연산하는거다 그래서 기본적으로 boolean형식으로 값이 나온다.

 

Logical Not Operator

not 연산은 불 연산을 뒤집는연산이다.

만약 참에 대해 not 을 붙이면 거짓이 되고

거짓에 대해 not을 붙이면 참이된다.

 

not 연산자는 느낌표를 붙이면된다.

 

ex1:

        var target = false
        print(!target)
        
        target = !target
        print(target)

결과:

true

true

 

처음 target이 false이지만 print할때 ! 를, not 연산자를 붙여서 print할땐 true가 출력된다.

그다음엔 target에 !target을 해서 true값으로 변경된다.

 

Logical And Operator

and 연산은 두 불형식 값이 모두 참일때 참으로 결과가 나오는 연산이다.

 

연산자로는 &&를 사용한다.

단 여러 if문이 겹치는형태를 축약하는 형태로 같은 and역할을 하게 끔 할 수도 있다 

ex2)

        let target1 = false
        let target2 = true
        let target3 = true
        
        if target1 && target2 {
            print("이 글은 출력되지 않는다.")
        }
        
        if target2 && target3 {
            print("두 값은 모두 참이다.")
        }
        
        if target2, target3 {
            print("이렇게도 쓴다.")
        }
        
        if target1, target3 {
            print("이 글은 출력되지 않는다.")
        }

결과:

두 값은 모두 참이다.

이렇게도 쓴다.

 

 

 

Logical Or Operator

and를 했으니 이제 or이다.

둘중 하나만 참이면 참으로 결과가 나오는 연산이다.

 

위의 예제를 살짝 바꿔서 아래 예시를 만들어봤다.

 

ex2)

        let target1 = false
        let target2 = true
        let target3 = true
        
        if target1 || target2 {
            print("이 글이 출력된다")
        }
        
        if target2 || target3 {
            print("두 값중 하나는 참이다.")
        }

결과:

이 글이 출력된다

두 값중 하나는 참이다.

 

 

연산자 파트를 모두 끝냈다!

다음엔 어떤 기본 강좌를 할지는 모르겠다.

 

반응형

programmers.co.kr/learn/courses/30/lessons/42587

 

코딩테스트 연습 - 프린터

일반적인 프린터는 인쇄 요청이 들어온 순서대로 인쇄합니다. 그렇기 때문에 중요한 문서가 나중에 인쇄될 수 있습니다. 이런 문제를 보완하기 위해 중요도가 높은 문서를 먼저 인쇄하는 프린

programmers.co.kr

이번 문제는 배열이 주어지고 정렬하는 문제로 보았다.

 

실 답을 구하려면 굳이 id를 정하고 정렬할 필요는 없어 보이지만 

실제 프린터라고 생각해서 하나의 작업에 대해 id를 각각 추가하여 작성해봤다.

 

정렬 순서는 문제에서 제시해준 3단계를 그대로 따라하면 간단히 된다.

 

난 총 세부분으로 소스를 나눠 작성했다

 

첫번째 - 주어진 priorities에 각각 id를 지정

두번째 - priorities를 정렬(

문제에서 주어짐, 첫번째 목록의 우선순위가 대기중인 작업의 우선순위보다 낮다면 무조건 대기순위 뒤로 이동 

대기중인 작업중 우선 순위가 더 큰게 없다면 대기순위에서 제거)

 

세번째 - 찾으려는 타깃이 언제 완료되는가 리턴 

 

 

  struct Work {
    let id:Character
    let priority:Int
  }
  
  func solution(_ priorities:[Int], _ location:Int) -> Int {
    var works = [Work]()
    var endArray = [Work]()
    var lastId:Character = "a"
    for priority in priorities {
      let work = Work(id: lastId, priority: priority)
      lastId = Character(Unicode.Scalar((lastId.unicodeScalars.first!.value) + 1)!)
      works.append(work)
    }

    guard location < works.count else { return -1 }
    let findTarget = works[location]
    
    while works.count > 0 {
      let target = works[0]
      var notFind = true
      for i in 1 ..< works.count {
        // 뒷 대기열에 큰게 있다면 notFind = false
      }
      if notFind {
      // 대기열에서 제거 endArray에 추가
      }
    }

    for i in 0 ..< endArray.count {
    // 찾으려는 id와 같은것의 i값 리턴
    }
    
    return -1
  }

 

 

전체 소스는 아래 깃허브 페이지에 있다.

github.com/wiwi-git/Programmers-learn/blob/master/swift/Practice/Practice/Printer.swift

 

wiwi-git/Programmers-learn

Contribute to wiwi-git/Programmers-learn development by creating an account on GitHub.

github.com

 

반응형

+ Recent posts