[ swift ] 랜덤으로 여러 글을 띄워보자. + 중복 클릭 방지 방법

sonny·2025년 1월 21일
0

TIL

목록 보기
109/133

어우 요즘 블로그 작성하기 꽤나 힘들다.

메인 뷰를 만들다가 이미지뷰 아래에 랜덤으로 기분 좋은 글을 넣어서 이미지뷰를 탭할 때 마다 랜덤으로 내가 작성해놓은 한마디가 나왔으면 좋겠다.

열심히 적었는데 너무 정상적인 것 같아서 나중에 살짝 재밋게도 바꿔보고 싶다.

아 그리고 랜덤으로 문구를 보이게 하려고 알아보다가 CaseIterable 이라는 프로토콜을 알게 되었는데,

CaseIterable 이용했다.

CaseIterable
은 Swift 표준 라이브러리에서 제공하는 프로토콜이라고 한다.

열거형(enum)의 모든 케이스를 배열 형태로 사용할 수 있게 해주는 기능을 제공한다는데 그렇게 된다면 열거형의 모든 케이스를 쉽게 순회할수가 있게 되는 것이고, 랜덤으로 선택하는 등의 작업도 할 수가 있다.

    static let firstMessage = "소소하고 확실한 행복으로 행담이를 깨워볼까요?"
    
    // 랜덤으로 메세지 반환하기 (firstMessage는 제외하기)
    static func getRandomMessage() -> String {
        return Self.allCases.filter { $0.rawValue != firstMessage }.randomElement()?.rawValue ?? "오늘의 소확행 기록하기."
    }

그 다음 아래에 랜덤으로 나올 수 있게끔 코드를 짰다.

static을 써서 정적 메서드이기에 객체를 생성하지 않아도 호출이 가능하게 했고, Self의 경우 현재 타입을 나타내준다.

평소에 소문자 self를 많이 썼는데 대문자 Self의 경우에는 현재타입. 그러니까 클래스나 구조체 열거형을 나타내는 키워드라서 주로 타입의 정적 맥락에서 사용된다고 한다.

정적 맥락이라 함은 정적인 속성이나 메서드, 프로토콜 관련 코드다.

그러니까 내가 지정한 정적 메서드나 프로토콜 구현에서 타입 그 자체를 가리킨다는 것.

그럼 저 Self가 가르키는건 위 사진에 있던 enum MainMessages 타입을 가리킬 것이다. (아님말고)

정리

키워드의미사용 위치예시
Self현재 타입 (클래스, 구조체, 열거형)정적 메서드/속성, 프로토콜Self.allCases, Self.init()
self현재 인스턴스인스턴스 메서드/속성self.value, self.method()

아무튼 Self.allCases 는 현재 타입을 나타내고 아까 사용한 CaseIterable 프로토콜을 따르는 타입에서 모든 케이스를 배열로 반환하는 속성이다.

그럼 배열로 변신을 했을 것이고,

 return Self.allCases.filter { $0.rawValue != firstMessage }.randomElement()?.rawValue ?? "오늘의 소확행 기록하기."

그럼 이어서 filter를 이용해 allCases로 가져온 배열에서 firstMessage 와 동일하지 않은 값들만 남겨줘야한다.

저기서 rawValue는 enum 케이스에서 원시 값을 뜻하는것이고, 배열에서 firstMessage와 원시값이 동일하지 않은 것만 추려내줄 것이다.

그리고 .randomElement()?.rawValue 는 이제 필터링이 완료된 배열에서 랜덤으로 하나를 선택하게 된다.

randomElement는 배열에서 임의로 하나를 선택하는 거고 반환타입으로는 선택된 요소의 옵셔널 값인데,

rawValue를 호출해서 선택이 된 enum 안에 있는 원시값을 가져오고, 혹여라도 만약 배열이 비어있다면 옵셔널 바인딩이 실패하고 nil이 될 것이다.

그리고 filter 결과가 빈 배열일 때 기본 메세지는 "오늘의 소확행 기록하기."가 되는 것.


우선 메세지만 따로 파일을 분리해서 저렇게 마무리를 하고,

컨트롤러로 돌아가서 isFirstTap의 Bool 값을 지정해준다.

이유는 첫번째 탭 여부를 추적하기 위함인데, 우선 초기값을 true로 두면 앱이 실행됐을 때 아직 탭하지 않았음을 의미하기에 해놨다.

사실 나중에는 알이 부화되었는지의 여부로 저걸 변경해야하는건데, 지금은 우선 구현만 해야하니 이렇게 지정했다.

    private func configureFirstMessage() {
        // 앱 초기 실행 시 첫번째 문구 설정
        mainView.updateMessage(MainMessages.firstMessage)
    }
    
    private func addGesture() {
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(imageViewTap))
        mainView.circularImageView.addGestureRecognizer(tapGesture)
        mainView.circularImageView.isUserInteractionEnabled = true
    }
    
    @objc private func imageViewTap() {
        // 연속 클릭 방지
        mainView.circularImageView.isUserInteractionEnabled = false // 클릭 비활성화
        
        if isFirstTap {
            isFirstTap = false // 첫번째 클릭 이후 상태 변경
        }
        mainView.updateMessage(MainMessages.getRandomMessage())
        
        // 2초 후 다시 활성화
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
            self.mainView.circularImageView.isUserInteractionEnabled = true // 클릭 재활성화
    }
}

그리고 이어서 코드를 작성했다.

configureFirstMessage 메서드에 있는 updateMessage는 뷰에 코드를 넣어두었는데

이렇게 문구 업데이트 역할을 따로 캡슐화 해서 뷰에 넣어두었는데 이렇게 하면 messageLabel을 외부에 노출하지 않아 뷰의 내부 구현을 보호 할 수 있게 된다.

아무튼 이어서

    private func configureFirstMessage() {
        // 앱 초기 실행 시 첫번째 문구 설정
        mainView.updateMessage(MainMessages.firstMessage)
    }

이 메서드는 앱 초기 실행시에 첫번째 메세지를 설정해주는 메서드다.
여기서 mainView.updateMessage(MainMessages.firstMessage)를 호출하고 MainMessages의 firstMessage를 MainView에 표시해준다.

그리고

    private func addGesture() {
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(imageViewTap))
        mainView.circularImageView.addGestureRecognizer(tapGesture)
        mainView.circularImageView.isUserInteractionEnabled = true
    }

addGesture메서드는 이미지뷰를 눌렀을 때 탭 이벤트를 설정해주는 것이다.

UITapGestureRecognizer이라고 UIKit에서 제공하는 제스처 인식 클래스 중 하나인데,

탭(한 번 또는 여러 번 화면을 터치하는 동작)을 감지하고 처리하는 데 사용되는 거라고 한다.

그리고 .addGestureRecognizer(tapGesture)는 특정 뷰에 제스처 인식기를 추가하는 메서드인데, 이 호출로 circularImageView가 "탭" 동작을 감지할 수 있게 된다.

그러면 이제 탭동작을 감지하고 해당 동작이 발생하면 imageViewTap 메서드가 실행 된다.

그리고 mainView.circularImageView.isUserInteractionEnabled = true 이 코드는 잘 몰랐는데 이 코드의 역할이 뷰의 사용자 상호작용을 활성화한다고 한다.

기본적으로 UIImageViewsms는 isUserInteractionEnabled 값이 false 설정이 되어있기 때문에 터치 이벤트를 감지하지 못한다고 한다 (몰랐다)

그래서 이렇게 하면 터치와 제스처 이벤트를 감지할 수 있도록 활성화를 하는건데, 이건 이미지뷰에 터치를 인식하려면 반드시 설정해야하는 부분이라고 한다.

중복 클릭 방지하기

    @objc private func imageViewTap() {
        // 연속 클릭 방지
        mainView.circularImageView.isUserInteractionEnabled = false // 클릭 비활성화
        
        if isFirstTap {
            isFirstTap = false // 첫번째 클릭 이후 상태 변경
        }
        mainView.updateMessage(MainMessages.getRandomMessage())
        
        // 2초 후 다시 활성화
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
            self.mainView.circularImageView.isUserInteractionEnabled = true // 클릭 재활성화
    }
}

이 코드가 탭 이벤트 처리와 함께 연속 클릭 방지도 처리하는 코드다.

우선 한번 탭하게되면 중복을 방지하고자 아까 말했던 isUserInteractionEnabled 값을 false로 다시 지정을 해준다.

false로 해야 모든 상호작용이 무시되기 때문에 바로 지정을 해주고,

if isFirstTap { isFirstTap = false } 이 부분은 이 탭이 처음 탭인지 확인해야해서 넣은 것이다.

만약 첫번째 탭이 true라면 내부로직을 실행해서 false로 설정해두면 두번째 탭부터는 아까 지정한 랜덤 메세지가 나오게 된다.

그리고 DispatchQueue를 이용해서 2초뒤에 다시 실행할 수 있도록 비동기 메서드를 사용한다.

(deadline: .now() + 2.0) 이 현재부터 2초 뒤를 의미하는거고 클로저 안에 있는 작업이 2초뒤에 실행이 되는 것이다.

클로저 안에는 다시 탭을 할 수 있게 true 값을 넣어두었다.

연속클릭 방지를 하면 앱이 불필요한 작업을 반복하지 않아도 되어 좋다.

잘 된다~

profile
iOS 좋아. swift 좋아.

0개의 댓글

관련 채용 정보