티스토리 뷰

반응형

안녕하세요 :) Zedd입니다.

오늘...!어떤 분이 질문을 주셨어요.



제가 저 때 컴퓨터 앞이 아니라서...일단 해봐야 할 것 같다고 말씀드렸는데, 참 해볼만한 주제? 죠?

저분이 보신 글은 <Label 부분 글자 크기/폰트/색상 변경방법>에요.

이런식으로 UILabel에서, 부분문자열의 attribute를 바꾸는 내용인데,

저걸 해보면

(위에서 have가 hava로 오타가..)


문장에서 zedd가 많이 나옴에도 불구하고 딱 첫번째 zedd만 attribute가 바뀌죠.

질문은 여기서 나오는 모든 zedd의 attribute를 못바꾸냐!!!였습니다.


뭐든...하면 되겠죠?

제가 생각만 해본거는 일단 


1
2
let range = (text as NSString).range(of: "zedd")
self.myLabelChangeColor(text, range: range )
cs


우리가 이런식으로 하고 있었잖아요? NSString으로 바꿔서 쉽게 range를 구해서...해당 range. 즉 zedd의 색상을 바꿔라...라는 식으로 했었어요.


1
2
3
4
5
6
7
func myLabelChangeColor(_ text: String, range: NSRange){ 
 
     let attributedString = NSMutableAttributedString(string: text)

     attributedString.addAttribute(.foregroundColor, value: UIColor.blue, range: range)
 
     self.myLabel.attributedText = attributedString
}
cs


그리고 이렇게...해줍니다. 

range는 text를 쫙 훑고 첫번째로 일치하는 것을 만나면 종료하는 것 같네요. 그러니까 가장 첫번째로 만난 zedd만 색상이 바뀌었겠죠. 

실제로 range의 정의는 

"Finds and returns the range of the first occurrence of a given string within the receiver"랍니다.

첫번째로 일치하는 것을 찾으면 바로 해당 range를 리턴하는 것이죠. 


그럼 일단 우리는 하는 방법은 모르겠어요!!!근데 이거는 생각할 수 있겠죠.

아 우리 text안에 있는 "zedd"라는 range를 "모두" 구해서 attribute를 바꿔주면 안될까? 

네!!!! 그러면 됩니다.


자.. 그럼 range를 찾아야하는데....저는 range쥐약이에요...아 언제한번 range도 제대로 한번 공부해야겠음...

암튼....여러분 생각합시다. 어렵게 생각하지마세요.

우리가 


"hello, zedd. Zedd have a zeddyzedd!"


이라는 문자열이 있다고 했을 때, 여기서 "zedd"만 찾고싶어요.

range라는 메소드는 앞에서 부터 search한다고 그랬죠? 

우리도 앞에서 부터 search합시다. 


"hello, zedd. Zedd have a zeddyzedd!"


zedd를 하나 만났네요!

그럼 일반 range메소드는 여기서 종료되게 됩니다. 

Finds and returns the range of the first occurrence of a given string within the receiver니까요.


하지만!!!

range에는 위에서 사용한 range(of:)도 있지만, 

func range(of searchString: String, options mask: NSString.CompareOptions = [], range rangeOfReceiverToSearch: NSRange) -> NSRange


도 있답니다. 가장 중요한 사실은 range를 구하는데 파라미터로 range를 받는 것이죠.

이 range메소드가 어떤 역할을 하는지 아시겠나요?


네. 그렇습ㄴ디ㅏ..!!!!! 파라미터로 받은 range안에서 찾으려는 searchString을 찾는 것이죠!!

하지만 역시나 가장 첫번째로 우리가 찾는 searchString과 일치하는 string을 찾으면 바로 해당 range를 리턴합니다.



그럼 이런 궁금증이 들 수 있겠죠.

아니 그럼;;;;;우리가 저~~기 위에서 사용한 range(of:)랑 지금 range도 파라미터로 받는 range(~~~어쩌고)랑 뭔차이냐;; 둘다 searchString이랑 일치하는 첫번째 string찾아서 바로 리턴하는거 아니냐;;;;;;;;


맞습니다...하지만, 이렇게 생각해볼까요? 우리가 사용할 range를 파라미터로 받는 range메소드

func range(of searchString: String, options mask: NSString.CompareOptions = [], range rangeOfReceiverToSearch: NSRange) -> NSRange

이거..
암튼 이걸 생각해봅시다. 


처음에는 range를 0부터 우리 문자열의 끝만큼(문자열의 길이만큼) 줍니다.


"hello, zedd. Zedd have a zeddyzedd!"


그럼 range는 {0, 35}가 나올거에요. 세보시면...35나옴..이까지는 아시겠죠.

그럼 여기서 이 {0, 35}를 가지고 zedd를 찾아라 ㅡㅡ 해볼게요.


"hello, zedd. Zedd have a zeddyzedd!"


그럼 range메소드는 zedd라는 string을 만났으니 여기서 종료하게 됩니다. range는 {7, 4}가 나올거고, 이까지는 저~~기 위에서 처음에 썼던 range(of:)랑 똑같죠?

 


하지만 이렇게 생각해봅시다. 

우리가 이 처음만난 "zedd"가 전체 문자열에서 어떤 range를 가지고 있는지 위에서 구했죠? 

그럼 이렇게 생각할 수 있어요.


"hello, zedd. Zedd have a zeddyzedd!"


아 이까지는 봤으니까 생각하지 말고, zedd를 찾은 다음 range부터 다시 탐색하자!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
====> range에 range라는 파라미터를 넣는다고 했죠? 그건 이 range안에서 탐색하겠다는 의미였죠? 
== hello, zedd.를 제외한 range를 구할 수 있죠?
==> 이걸 반복하면됨.

무슨소리인지 아시겠나요???!?!?

hello, zedd. 까지 봤으니, 이제 hello, zedd. 다음부터 보도록 range를 "주면" 됩니다. 
우리는 "hello, zedd."에서 "zedd"만 찾아서 그 range를 얻었죠? 이 얻은 range로 다음에 볼 range를 구할 수 있는 것이죠. location과 length가 나오기 때문입니다.

지금 똑같은 말을 계속 반복하고 있는 것 같은데...이해가 안가실까봐....일단 코드를 보도록 할게요.

func changeAllOccurrence(entireString: String,searchString: String){
 
        let attrStr = NSMutableAttributedString(string: entireString)
        let entireLength = entireString.count
        var range = NSRange(location: 0, length: entireLength)
cs


자..메소드의 일부입니다. 먼저 파라미터로 전체 문자열과 찾으려는 문자열(제가 든 예제에서는 zedd가 되겠죠?)을 받네요.

그리고 내 전체 문자열을 NSMutableAttributedString으로 만들어줍니다. 나중에 label에 attributedText로 넣어줘야되니까..!! 


그리고 전체 문자열의 길이를 구해줍니다. 일단은요..! 이거는 이따가 쓸거에요. 

그리고 range를 만들어줍니다. 0부터 전체 문자열 길이만큼 ㅇㅇ


"hello, zedd. Zedd have a zeddyzedd!"


왜냐? 지금은 처음이니까..!!


func changeAllOccurrence(entireString: String,searchString: String){
 
        let attrStr = NSMutableAttributedString(string: entireString)
        let entireLength = entireString.count
        var range = NSRange(location: 0, length: entireLength)
 
        var rangeArr = [NSRange]()
cs


그리고 저는 NSRange타입의 rangeArr를 하나 만들어줄게요 :) 


while (range.location != NSNotFound) {
            
            range = (attrStr.string as NSString).range(of: searchString, options: .caseInsensitive, range: range)
            rangeArr.append(range)
            if (range.location != NSNotFound) {
 
                range = NSRange(location: range.location + range.length, length: entireString.count - (range.location + range.length))
            }
            
        }
cs


그리고 while문을 돌아줍니다..!

왜냐? 우리는 한번 찾으면 끝나는게 아니라, 계속 찾아야 하니까. 

그럼 이런 궁금증이 들 수 있어요.

range의 loaction이랑...NSNotFound?..이게 모야;;;;

=> range메소드에는, 



이런 설명이 있어요. 해당 range안에 우리가 찾으려는 문자가 없으면 NSNotFound를 리턴해주는 거랍니다. 

예를들어, 

let range = ("안녕하세요" as NSString).range(of: "by")
print(range)//{9223372036854775807, 0}
cs


"안녕하세요"라는 문자열에 by라는 문자열이 있는 range를 찾고싶어요. 근데 "by"라는게 없죠? 그러면 range는 {NSNotFound, 0}이 나오게 된답니다.

위에는 숫자가 엄청 큰게 나왔는데, 저게 NSNotFound에요 :)

아무튼 

while (range.location != NSNotFound) {
cs

의 뜻은, 내가 여기서 "zedd"를 발견하지 못할 때 까지 반복하겠다~~라는 말이죠.



while (range.location != NSNotFound) {
            
     range = (attrStr.string as NSString).range(of: searchString, options: .caseInsensitive, range: range)
     rangeArr.append(range)
cs


자..그리고 위에서 우리가 선언했던 range를 다시 어떤 range를 구해서 넣어줍니다. ㅇㅇ

options에는 zedd든 Zedd든 아무튼 대소문자 구분없이 찾게 했는데 이건 취향대로

아무튼 저 코드 줄을 한번 봅시다. 

 range = (attrStr.string as NSString).range(of: searchString, options: .caseInsensitive, range: range)
cs


파라미터로 들어간 range는 아까 우리가 위에서 만든 그 range일 거에요. 즉!!!


var range = NSRange(location: 0, length: entireLength)
cs


이거죠. 처음부터 끝까지. 

그러면 


"hello, zedd. Zedd have a zeddyzedd!"


처음부터 끝까지 보면서 "zedd"를 찾을거에요. 그럼


"hello, zedd. Zedd have a zeddyzedd!"


여기서 zedd를 만나게 되죠. 그리고 바로 종료하겠죠? 처음 매치된걸 만나면 해당 range를 리턴해주니까요.

그럼 구한 range를 우리가 아까 만든 NSRange배열에 넣어줍니다. 


rangeArr.append(range)
cs

그리고 이제 뭘 해야 할까요. 

"hello, zedd. Zedd have a zeddyzedd!"


위에서 계속 말했던것 처럼 이제 회색 처리한 부분은 안봐도 되는거겠죠?

우리가 지금 while문 안에 있으니까 


"Zedd have a zeddyzedd!"


우리는 애초에 이런 문자열이 있었다는 듯이, while문을 계속 돌려주면 됩니다. 

그럼 range를 또 수정해야겠네요.


if (range.location != NSNotFound) {
 
    range = NSRange(location: range.location + range.length, length: entireString.count - (range.location + range.length))
}
cs


또 range를 수정해주게 되는데, 잘 봅시다. 파라미터로 들어가는 range들은 다 아까의 range에요. 

즉 "hello, zedd. Zedd have a zeddyzedd!"에서의 "zedd"의 range죠. 

그럼 

"hello, zedd. Zedd have a zeddyzedd!"처럼 

hello, zedd.를 range에서 제외시킬려면,

새로운 range의 시작, 즉 location을 zedd의 location + zedd의 length를 더한 값으로 해주면 되겠죠?


그러면 일단 새로운 range의 시작은 



0부터 35까지의 range가 있을 때, 

"hello, zedd. Zedd have a zeddyzedd!"에서의 처음 만난 "zedd"는, 위치가 7이고, 길이가 4짜리였죠? 

그 둘을 더한 11이 이제 새로운 range의 시작이 되는 것입니다. 그럼 새로운 range의 length는 어떻게 줘야 할지 이제 조금 감이 오시나요? 


"hello, zedd. Zedd have a zeddyzedd!

이만큼의 길이만 구하면 되는 것이죠..!!


그럼 저 길이는??? == 전체 문자열의 길이 - (현재 range의 location과 length를 더한 값)

이 되겠죠...!!!!!!!!! 우리는 딱 "zedd"의 range를 알기 때문에, zedd 앞에 어떤 문자열이 있든 zedd가 뒤에 있음으로서 앞은 계산 안해도 되는 것이죠.


그래서 결국 length는  == 전체 문자열의 길이인 35 - (7 + 4) = 24가 되는 것입니다.


while (range.location != NSNotFound) {
            
      range = (attrStr.string as NSString).range(of: searchString, options: .caseInsensitive, range: range)
      rangeArr.append(range)
      if (range.location != NSNotFound) {
        range = NSRange(location: range.location + range.length, length: entireString.count - (range.location + range.length))
                
      }
            
   }
cs


결국 다시 while문 조건을 검사하게 되고....계속 range를 검사하면서 "zedd"가 나오는 range를 전부 range배열에 담게 되는 것이죠.


그럼 이제 우리는 "zedd"가 나오는 range를 모두 알았으니, 이제 뭘 하면 될까요? 

네..!!이 range에 attribute를 추가해주면 됩니다.


rangeArr.forEach { (range) in
       attrStr.addAttribute(.foregroundColor, value: UIColor.blue, range: range)
}
cs


바로 이렇게요.


그리고 메소드 마지막에...!!!


self.myLabel.attributedText = attrStr
cs

이렇게만 해주면..!!


이렇게 zedd라는 문자열에 모두 attribute가 들어가게 됩니다 :)

뭔가 차근차근 이해하게 하고 싶어서 엄청 구구절절 설명한 느낌이 없지않아 있는데........제 설명이 이해되었길 바라며...



오늘도 도움이 되었길 바래요 XD

이걸 오늘에서야 다 쓰다니...


반응형