반응형

Swift 에서는 접근레벨을 5개로 나뉜다

open

public

internal

fileprivate

private

 

open은 말그대로 open된 소스고 public은 오픈에서 오버라이드와 서브클래싱을 막는다

이렇게 점점 접근 가능한 범위를 줄이고

마지막에 와서는 private, 해당 내역이 선언된 구역에서만 사용가능하게 된다

 

swift에서는 변수의 get 과 set을 재정의를 해주는게 간편히 가능한데

이와 위의 접근레벨과 섞으면 내부에선 읽고 쓰고, 외부에서는 읽기만 가능한게 만들 수 있다.

단순히 둘이 섞어보자면 아래와같게 하여 값을 보호할 수 있다.

public class AccessLevels {
    private var realValue: String = ""
    public var value: String {
        get {
            return realValue
        }
    }
    
    func function() {
        realValue = "real value"
    }
}

let test: AccessLevels = .init()
print(test.value) // "\n"
test.function()
print(test.value) // real value

콘솔 출력값은 주석처리한 부분이다

다른 언어에서 get, set 함수를 따로 만들어 사용하는것과 유사하다

실제로 사용하는 변수는 realValue이고 외부에서 해당 값을 보기위해 realValue를 접근하려고하면 오류가 나고

이를 보기위해서는 value를 봐야한다.

 

사실 이를 더 간편히 만들 수 있는게 있는데 이게바로 이글의 주제인 private(set) 이다.

public class PrivateSet {
    private(set) var value: String = ""
    
    func function() {
        value = "real value"
    }
}
let test: PrivateSet = .init()
print(test.value) // "\n"
test.function()
print(test.value) // "real value"

PrivateSet 클래스는 위의 AccessLevels 클래스와 동일한 기능을 하는 클래스이다

get을 지정해주는 변수와 실제 값을 담아두는 변수가 private(set) 으로 value 하나로 통일되었다.

 

외부에서 값이 변경되지 않도록 지켜주는 기능또한 여전하다.

 

private(set) 간편!

반응형

들어온 데이터를 같은 날짜별로 분리해야할 일이 생겨 오랜만에 포스팅에 좋겠다 싶어서 남긴다.

스위프트의 Dictionary 는 무려 이러한 분할을 조건만 명시해주면 알아서 해준다.

이걸 어떻게 구현해야할지 막막해지며 포문안에 키값을 추출하며 분리해 나가야하나 싶었는데

그냥 알아서 제공해줘서 매우 기쁘다.

공식 문서는 아래와 같다.

https://developer.apple.com/documentation/swift/dictionary/3127163-init

 

Apple Developer Documentation

 

developer.apple.com

 

생성자 형태.

init<S>(grouping values: S, by keyForValue: (S.Element) throws -> Key) 
rethrows where Value == [S.Element], S : Sequence

 

 

형태는 S를 줘서 키값을 리턴해주면된다.

바로 코드로 테스트해보면

데이터 형태는 TestItem, 테스트 하기 편하게 id와 updateAt 만 주고

struct TestItem: Codable {
    let id: Int
    let updatedAt: String?
}

 

받은 데이터는 아래와같은거로 가정 16,17은 업데이트 값이 이상하게 들어간 경우를 테스트

let testArray: [TestItem] = [
    .init(id: 0, updatedAt: "2022-04-22T13:18:08.000Z"),
    .init(id: 1, updatedAt: "2022-04-22T13:18:08.000Z"),
    .init(id: 2, updatedAt: "2022-04-08T13:18:08.000Z"),
    .init(id: 3, updatedAt: "2022-04-07T13:18:08.000Z"),
    .init(id: 4, updatedAt: "2022-04-07T13:18:08.000Z"),
    .init(id: 5, updatedAt: "2022-04-05T13:18:08.000Z"),
    .init(id: 6, updatedAt: "2022-03-20T13:18:08.000Z"),
    .init(id: 7, updatedAt: "2022-03-17T13:18:08.000Z"),
    .init(id: 8, updatedAt: "2022-03-17T13:18:08.000Z"),
    .init(id: 9, updatedAt: "2022-03-16T13:18:08.000Z"),
    .init(id: 10, updatedAt: "2022-02-08T13:18:08.000Z"),
    .init(id: 11, updatedAt: "2022-01-08T13:18:08.000Z"),
    .init(id: 12, updatedAt: "2021-01-08T13:18:08.000Z"),
    .init(id: 13, updatedAt: "2021-04-08T13:18:08.000Z"),
    .init(id: 14, updatedAt: "2021-04-30T13:18:08.000Z"),
    .init(id: 15, updatedAt: "2020-04-01T13:18:08.000Z"),
    .init(id: 16, updatedAt: "2020-04-0113:18:08.000Z"),
    .init(id: 17, updatedAt: nil),
]

 

이제 그룹핑하고 찍어보자면

updatedAt이 제대로 입력되지 않은 녀석은 blank라는 키로 따로 뺏다.

여기선 검사를 안했지만 정규식패턴으로 updatedAt을 검사하는 과정을 넣는게 좋을듯 보임

let groupedDict: [String: [TestItem]] = Dictionary(grouping: testArray)
{ item in
    guard let updatedAt: String = item.updatedAt else { return "blank"}
    let split = updatedAt.split(separator: "T")
    guard split.count == 2 else { return "blank" }
    let dateString: String = String(split[0])
    return dateString
}

for key in groupedDict.keys {
    print("key : \(key)")
    let values: [TestItem] = groupedDict[key]!
    for value in values {
        print("id: \(value.id), updatedAt: \(value.updatedAt ?? "")")
    }
    print("")
    print("")
}

 

출력:

key : 2021-01-08
id: 12, updatedAt: 2021-01-08T13:18:08.000Z


key : 2022-04-07
id: 3, updatedAt: 2022-04-07T13:18:08.000Z
id: 4, updatedAt: 2022-04-07T13:18:08.000Z


key : 2020-04-01
id: 15, updatedAt: 2020-04-01T13:18:08.000Z


key : blank
id: 16, updatedAt: 2020-04-0113:18:08.000Z
id: 17, updatedAt: 


key : 2022-03-20
id: 6, updatedAt: 2022-03-20T13:18:08.000Z


key : 2022-02-08
id: 10, updatedAt: 2022-02-08T13:18:08.000Z


key : 2022-01-08
id: 11, updatedAt: 2022-01-08T13:18:08.000Z


key : 2022-03-17
id: 7, updatedAt: 2022-03-17T13:18:08.000Z
id: 8, updatedAt: 2022-03-17T13:18:08.000Z


key : 2022-04-08
id: 2, updatedAt: 2022-04-08T13:18:08.000Z


key : 2022-03-16
id: 9, updatedAt: 2022-03-16T13:18:08.000Z


key : 2022-04-05
id: 5, updatedAt: 2022-04-05T13:18:08.000Z


key : 2022-04-22
id: 0, updatedAt: 2022-04-22T13:18:08.000Z
id: 1, updatedAt: 2022-04-22T13:18:08.000Z


key : 2021-04-30
id: 14, updatedAt: 2021-04-30T13:18:08.000Z


key : 2021-04-08
id: 13, updatedAt: 2021-04-08T13:18:08.000Z

딕셔너리는 순서를 지키지 않기에 

만약 데이터로 가공해야한다면 키값에 대한 정렬이 필요해보인다.

반응형
반응형
반응형

scrollView를 받고있는 collectionView는 베이스와 같이 스크롤을 프로그래밍으로 움직이는게 가능하다

이를 간편하게 만들어둔게 scrollToItem( at,at, animated )

간편해서 

indexPath만 따로 만들어두면 곧장 해당 아이템이 있는곳으로 날아갈수있다.

 

iOS 15 시뮬레이터에서 돌려보던중 해당 기능이 동작하지 않는걸 발견했는데 도저히 이유를 모르겠던중 아래와같은 답변을 찾았다

https://developer.apple.com/forums/thread/663156

 

UICollectionView scrollToItem brok… | Apple Developer Forums

Hi All Where we have to put this code? self.collectionView.isPagingEnabled = false self.collectionView.scrollToItem(at: indexPath, at: .left, animated: false) self.collectionView.isPagingEnabled = true In my scenario, Let's suppose there are 5 items to sho

developer.apple.com

 

iOS14에서 나온 문제건만..

iOS15에서도 고쳐지지 않았다

 

문제가된 경우는

scolltoITem하기전 isPgingEnabled가 true로 설정해둘경우이다

그래서 

아래와같이 하면된다.

item번호 11번으로 가는 코드이다.

collectionView.isPagingEnabled = false
collectionView.scrollToItem(at: IndexPath(item: 11, section: 0), at: .centeredHorizontally, animated: true)
collectionView.isPagingEnabled = true

scrollToITem내부 파라미터에대해서는 공식 홈페이지를 보시고...

해당 함수를 호출하기전 paging을 꺼둔다.

그리고 다시킨다

 

어처구니없는 해결방법이지만 동작을 잘해서 그냥 해당방법으로 가기로 결정했다.

 

반응형

오랜만에 포스트를 남겨야지하다가 이건 남겨도 괜찮겠다 싶어서 남깁니다.

func moneyPlaceHolder(money: Int) -> String {
    guard money >= 0 else { return "" }
    
    let moneyString = String(money)
    let moneyCount = moneyString.count
    let insertPlaceCount = 3
    
    if moneyCount <= insertPlaceCount {
        return moneyString
    }
    
    let index0: String.Index = moneyString.startIndex
    var resultString = ""
    var loopIndex = 0
    for i in (0 ..< moneyCount).reversed() {
        resultString += String(moneyString[moneyString.index(index0, offsetBy: i)])
        loopIndex += 1
        if loopIndex >= insertPlaceCount {
            resultString += ","
            loopIndex = 0
        }
    }
    return String(resultString.reversed())
}

테스트 값:

print(moneyPlaceHolder(money: 1355500))
print(moneyPlaceHolder(money: 500))
print(moneyPlaceHolder(money: 1500))
print(moneyPlaceHolder(money: 034))
print(moneyPlaceHolder(money: 65))
print(moneyPlaceHolder(money: 0))
print(moneyPlaceHolder(money: 1))
print(moneyPlaceHolder(money: -30))

/**
  result
  1,355,500
  500
  1,500
  34
  65
  0
  1
  
*/

더 짧거나 효율좋게 할 수 있는 방법이 있으면 좋겠네요.

 

 

 

https://gist.github.com/wiwi-git/646b628dec8a7ed940c3d0cc885017fd

 

돈자리수표시.swift

GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

반응형

패스워드용 텍스트필드를 만들려면 Secure설정을 주면 된다.

 

하지만 이렇게 하면 포커스가 옴겨졌다 다시 텍스트필드를 수정하려고하면 기존 텍스트 내용이 날라간다.

 

보안이 필요한 텍스트필드니 당연하다 생각했는데

 

이걸 원치 않는 사람이 있나보다

 

수정되는 이전 값이 남아 있고, 값이 숨기고 보이고 할 수 있고 그런 텍스트 필드를 원하는데 어떻게 해야할까요?

 

라는 질문을 글을 보고 이렇게 하면 되지 않을까 하고 해봤는데

 

솔직히 별로 맘에 안든다.

 

다른 방법이 없으려나

 

보안이 보안답지 않고....

그렇다고 소스가 깔끔하지도 않고....

 

나중에 텍스트필드를 새로 클래스 만들어서 하면 좀 깔끔해질거같긴한데 

미묘하다.

 

 

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

 

GitHub - wiwi-git/Hide_And_Show_TextField: 포커스 옴겨도 사라지지 않고 숨기고 보이고 할 수 있는 패스워

포커스 옴겨도 사라지지 않고 숨기고 보이고 할 수 있는 패스워드용 텍스트필드가 어떻게 만들까 생각하다 끄적여보는 레포지토리 - GitHub - wiwi-git/Hide_And_Show_TextField: 포커스 옴겨도 사라지지 않

github.com

 

var textField: UITextField!
    var showButton: UIButton!
    private var hiddenText: String = "" {
        didSet{
            print("hiddenText: " + hiddenText)
        }
    }
    var isShowMode = false
    
    let secretChar = "*"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        textField = createTextField()
        showButton = createShowButton()

        view.addSubview(textField)
        view.addSubview(showButton)
        
        let viewGuide = view.safeAreaLayoutGuide
        textField.translatesAutoresizingMaskIntoConstraints = false
        showButton.translatesAutoresizingMaskIntoConstraints = false
        [
            textField.topAnchor.constraint(equalTo: viewGuide.topAnchor, constant: 30),
            textField.leadingAnchor.constraint(equalTo: viewGuide.leadingAnchor, constant: 16),
            textField.trailingAnchor.constraint(equalTo: viewGuide.trailingAnchor,constant: -16),
            textField.heightAnchor.constraint(equalToConstant: textField.frame.height),
            
            showButton.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 8),
            showButton.trailingAnchor.constraint(equalTo: viewGuide.trailingAnchor,constant: -16),
            showButton.heightAnchor.constraint(equalToConstant: textField.frame.height),
            showButton.widthAnchor.constraint(equalToConstant: 100)
        ].forEach { $0.isActive = true }
    }
    
    func createTextField() -> UITextField {
        let textFieldSize = CGSize(width: 0, height: 35)
        let textField = UITextField(frame: CGRect(origin: .zero, size: textFieldSize))
        textField.layer.borderWidth = 1
        textField.layer.cornerRadius = 10
        textField.textContentType = .password
        textField.placeholder = "password"
        textField.keyboardType = .asciiCapable
        textField.isSelected = false
        
        textField.addTarget(self, action: #selector(textFieldEditingChangedAction(_:)), for: .editingChanged)
        return textField
    }

    func createShowButton() -> UIButton {
        let showButton = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 35)))
        showButton.setTitle("Set Show", for: .normal)
        showButton.setTitleColor(.systemBlue, for: .normal)
        showButton.backgroundColor = .clear
        showButton.setBackgroundImage(nil, for: .normal)
        showButton.layer.borderWidth = 1
        showButton.layer.borderColor = UIColor.systemBlue.cgColor
        showButton.layer.cornerRadius = 10
        showButton.addTarget(self, action: #selector(showButtonAction(_:)), for: .touchUpInside)
        
        return showButton
    }
    
    @objc func showButtonAction(_ sender: UIButton) {
        if ( sender.titleLabel?.text == "Set Show") {
            self.isShowMode = true
            sender.setTitle("Set Hide", for: .normal)
            sender.layer.borderColor = UIColor.clear.cgColor
            sender.backgroundColor = .darkGray
            sender.setTitleColor(.white, for: .normal)
            
            self.textField.text = hiddenText
            
        } else {
            self.isShowMode = false
            sender.setTitle("Set Show", for: .normal)
            sender.setTitleColor(.systemBlue, for: .normal)
            sender.backgroundColor = .clear
            sender.layer.borderColor = UIColor.systemBlue.cgColor
            
            textField.text = ""
            hiddenText.forEach { _ in
                textField.text! += secretChar
            }
        }
    }
    
    @objc func textFieldEditingChangedAction(_ textField: UITextField) {
        guard let last = textField.text?.last else {
            hiddenText = ""
            return
        }
        
        if textField.text?.count ?? 0 > hiddenText.count {
            if (!isShowMode) {
                textField.text?.removeLast()
                textField.text! += secretChar
            }
            hiddenText += String(last)
        } else {
            hiddenText.removeLast()
        }
    }
반응형

+ Recent posts