Development record of developer who study hard everyday.

레이블이 ios인 게시물을 표시합니다. 모든 게시물 표시
레이블이 ios인 게시물을 표시합니다. 모든 게시물 표시

Command not found problem on Mac M1

 Command not found problem on Mac M1

ios develop blog


I use Mac Pro M1 to develop Android and iOS app.

I encounter "Command not found" error when executing command on terminal. 

So, I go through this problem.


1. Why "command not found" occur?

This occurs because environmental variable does not exist.


2. How to solve

- Start terminal

- Open zshrc file

ex) vi ~/.zshrc

- Insert environmental path

ex) export PATH = example/program/path

- Apply zshrc file executing underneath code.

ex) source ~/.zshrc


Share:
Read More
, ,

How to play video in ios

 How to play video in ios

ios develop blog

This post introduce how to play video in ios.

One way is to play local video file and the other way is to play video with url.

Let's go


1. Video file

let filePath: String? = Bundle.main.path(forResource: "FastTyping", ofType: "mp4")

        let url = NSURL(fileURLWithPath: filePath!)

        

        let playerController = AVPlayerViewController()

        

        let player = AVPlayer(url: url as URL)

        playerController.player = player

        

        self.present(playerController, animated: true) {

            player.play()

        }


2. Video url

let url = NSURL(string: "https://dl.dropboxusercontent.com/s/e38auz050w2mvud/Fireworks.mp4")!

        

        let playerController = AVPlayerViewController()

        

        let player = AVPlayer(url: url as URL)

        playerController.player = player

        

        self.present(playerController, animated: true) {

            player.play()

        }


👇 You can see full code in my git.

https://github.com/antwhale/SampleMoviePlayer_ios.git


Share:
Read More
, ,

How to write, read and delete file in ios

 How to write, read and delete file in ios

ios develop blog

This post introduce file I/O in iOS.

Let's study how to write, read and delte file in iOS.


1. File Manager

lazy var fileManager = {

        return FileManager.default

    }()

To deal with File I/O, we need FileManager class.

FileManager class is convenient interface to the contents of the file system.


2. Variety of Directory in iOS


    

    lazy var desktopDirectory = {

        return fileManager.urls(for: .desktopDirectory, in: .userDomainMask).first

    }()

    

    lazy var documentDirectory = {

        return fileManager.urls(for: .documentDirectory, in: .userDomainMask).first

    }()

    

    lazy var libraryDirectory = {

        return fileManager.urls(for: .libraryDirectory, in: .userDomainMask).first

    }()

    

    lazy var userDirectory = {

        return fileManager.urls(for: .userDirectory, in: .userDomainMask).first

    }()

    

    lazy var downloadDirectory = {

        return fileManager.urls(for: .downloadsDirectory, in: .userDomainMask).first

    }()

    

    lazy var trashDirectory = {

        return fileManager.urls(for: .trashDirectory, in: .userDomainMask).first

    }()

There are diverse directory to deal with file in iOS.


3. Writing

        do {

            let fileName = "testFile.txt"    //1

            guard let filePath = documentDirectory?.appendingPathComponent(fileName) else { return }

            print("filePath: \(filePath.absoluteString)")

            

            let data = "Hello World!"

            try data.write(to: filePath, atomically: false, encoding: .utf8)    //2

        } catch let error {

            print("write error : \(error.localizedDescription)")

        }

1 => This is file name.

2 => Write this file. The parameter of atomically dictates whether you write auxiliary file.

If false, the file system write file directly to path.

If true, the file system write auxiliary file and then it is renamed to path.


4. Reading

       do {

            let fileName = "testFile.txt"

            guard let filePath = documentDirectory?.appendingPathComponent(fileName) else { return }

            print("filePath: \(filePath.absoluteString)")

            

            let data = try String(contentsOf: filePath, encoding: .utf8)    //1

            contentLabel.text = data

            

        } catch let error {

            print("read error : \(error.localizedDescription)")

        }

1 => Produces a string created by reading data from a given URL.


5. Deleting

       do {

            let fileName = "testFile.txt"

            guard let filePath = documentDirectory?.appendingPathComponent(fileName) else { return }

            print("filePath: \(filePath.absoluteString)")

            

            try fileManager.removeItem(at: filePath)    //1

        } catch let error {

            print("delete error : \(error.localizedDescription)")

        }

1 => Delete file placed on this path.


The full code is in below Github url.

https://github.com/antwhale/SampleFileIO_ios















Share:
Read More
, ,

How to solve ios issue about UITableViewCell textLabel deprecated

 How to solve ios issue about UITableViewCell textLabel deprecated


ios develop blog

When I practice ios app, I encounter warning about UITableViewCell's deprecated function.

The detail of warning is " 'textLabel' will be deprecated in a future version of iOS: Use UIListContentConfiguration instead, this property will be deprecated in a future release. "

The solution code for this warning is below:

 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)

        

        var content = cell.defaultContentConfiguration()

        content.text = items[(indexPath as NSIndexPath).row]

        content.image = UIImage(named: itemsImageFile[(indexPath as NSIndexPath).row])

                

        cell.contentConfiguration = content

        

        return cell

    }


Share:
Read More
, , , , ,

[번역] 스위프트 클로저 - Swift Closure

스위프트 클로저 - Swift Closure

스위프트 문법

스위프트 공식 문서를 번역한 글입니다.


클로저는 전달할 수 있고 코드에 사용될 수 있는 함수 블록입니다. 

스위프트의 클로저는 C와 Object-C에서와 비슷하고 다른 언어의 람다와 비슷합니다.

클로저는 정의된 곳의 context에서 상수와 변수를 참조하여 포획할 수 있습니다.

이것을 상수와 변수에 닫혀있다(closing over)라고 합니다.

스위프트는 포획의 메모리를 관리합니다.


전역함수나 내장함수는 특별한 형태의 클로저이다.

클로저는 아래 3가지 형태중 하나이다.

- 전역함수는 이름을 가지지만 어떤 값도 포획하지않는 클로저이다.

- 내장함수는 이름을 가지고 내장함수 안의 함수에서 값을 포획할 수 있는 클로저이다.

- 클로저 표현식은 context에서 값을 포획가능한 이름이 없는 클로저이다.


클로저는 다음과 같은 최적화를 제공한다.

- context로부터 파라미터와 반환값의 타입을 추정한다.

- 단일 표현 클로저의 암시적 리턴

- 축약된 인자 이름

- Trailing closure


클로저 표현식(Closure Expressions)

내장함수(Nested Function)는 큰 함수의 부분으로 코드 블록을 정의하기 편리하다.

그러나 때때로 완전한 선언과 이름 없이 간단한 방식의 함수같은 정의가 유용하다.

특히, 매개변수로 하나 이상의 함수나 메소드를 사용할 때 그렇다.


클로저 표현식은 간략하게 inline 클로저를 작성하는 방식이다.

클로저는 여러가지 짧은 형태의 문법적 최적화를 제공한다.

아래 sorted(by:) 메소드의 예를 참고하자.


정렬 메소드(The Sorted Method)

스위프트 표준 라이브러리는 sorted(by:)를 제공한다. 이 메소드는 정렬시키는 역할의 클로저에 맞게 배열의 값을 정렬한다.

정렬이 완료되면, sorted(by:) 메소드는 같은 타입과 크기의 새로운 배열을 반환한다.

원래의 배열은 수정되지 않는다.


아래의 클로저 표현식 예제는 알파벳 순서 반대로 배열을 정렬한다.

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]


sorted(by:) 메소드는 2개의 매개변수를 받고 정렬되었을 때, 첫번째 값이 두번째 값의 앞이나 뒤에 나와야하는지 Boolean 값으로 말해준다.

첫번째 값이 반드시 앞에 나와야하면 true, 아니면 false를 나타낸다.

이 예제는 String배열을 정렬하고 정렬시키는 closure의 타입은 (String, String) -> Bool 이다.

정렬시키는 closure를 작성하는 하나의 방법은 정확한 타입의 일반 함수를 작성하는 것이다.

그리고 sorted(by:) 메소드의 매개변수로 넘긴다.

func backward(_ s1: String, _ s2: String) -> Bool { return s1 > s2 } var reversedNames = names.sorted(by: backward) // reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]


클로저 표현식 문법(Closure Expression Syntax)

{ (<#parameters#>) -> <#return type#> in <#statements#> }

매개변수는 입출력 매개변수(in-out parameter)가 될 수 있다. 하지만 기본값은 가질 수 없다.

변수형 매개변수(Variadic parameters)는 사용될 수 있다.

튜플 역시 매개변수 타입과 리턴 타이븡로 사용될 수 있다.


아래 코드는 backward 함수를 이용한 클로저 표현 방식이다.

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 })


인라인 클로저에서 매개변수와 리턴타입의 선언은 backward 함수의 선언과 같다.

둘 다 (s1: String, s2: String) -> Bool이다.

그러나 inline 크롤저에서는, 매개변수와 리턴타입이 {} 안에 정의된다.

클로저의 몸통부분은 in 키워드에 의해 소개된다.

in 키워드는 클로저의 매개변수와 리턴타입의 정의가 끝이났고 클로저의 몸통부분이 시작한다고 말한다.


Context에서의 타입추론(Inferring Type From Context)

정렬시키는 클로저가 메소드에 매개변수로 넘어오기때문에, 스위프트는 반환 값과 매개변수들의 타입을 추론할 수 있다.

sorted(by:) 메소드는 String 배열에서 호출된다.

그래서 함수 타입은 (String, String) -> Bool 이여야만한다.

따라서 타입추론이 가능하고 (String, String) -> Bool 타입을 생략될 수 있다.

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

인라인 클로저 표현식에서 함수나 메소드에 클로저를 넘겨줄 때 파라미터와 리턴 타입을 추론하는 것은 항상 가능하다.

그 결과, 완전한 형태의 인라인 클로저를 작성할 필요가 없다.

그럼에도 불구하고, 원한다면 타입을 명시해도 된다. 그리고 타입을 명시하는 것은 코드를 읽는 사람이 모호하지 않다.


단일 표현 클로저의 암시적 반환(Implicit Returns from Single-Expression Closures)

단일 표현 클로저는 return 키워드를 생략함으로써 암묵적으로 단일 표현의 값을 반환할 수 있다.

아래 예시를 살펴보자.

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

여기서, sorted(by:) 메소드의 매개변수 타입은 Bool 값이 반환된다는 것을 명확하게 해준다.

왜냐하면 s1 > s2 단일 표현식이 Bool 값을 반환하기 때문이다.


매개변수 이름의 축약형(Shorthand Argument Names)

스위프트는 자동으로 인라인 클로저에 매개변수 축약형을 제공한다.

$0, $1, $2 이름들로 클로저의 매개변수 값을 참조할 수 있다.

축약형을 사용하면, 클로저의 정의에서 클로저의 매개변수를 생략할 수 있다.

축약형 매개변수의 타입은 예상되는 함수 타입에서 추론된다.

가장 높은 수의 축약형 매개변수는 클로저가 취하는 매개변수의 수를 의미한다.

in 키워드도 생략가능하다. 왜냐하면 클로저 표현식이 전체 body로 이루어져있기 때문이다.

reversedNames = names.sorted(by: { $0 > $1 } )


연산자 메소드(Operator Methods)

더 짧은 클로저 표현 방식이 있다.

스위프트의 String 타입은 2개의 String 값을 취하여 Bool 값을 반환하는 ">" 연산자를 가지고있다.

그래서 ">" 연산자를 간단하게 전달할 수 있다.

reversedNames = names.sorted(by: >)


후행 클로저(Trailing Closure)

함수에 마지막 매개변수로 클로저를 전달해야하는데 클로저 표현식이 길다면, 후행 클로저를 작성하는게 도움이 된다.

클로저가 함수의 매개변수라도, 함수의 괄호가 다 끝난 후 후행 클로저를 작성한다.

첫번째 클로저 argument label을 작성하지 않아도된다.

함수호출은 여러 후행클로저를 포함할 수 있다.

func someFunctionThatTakesAClosure(closure: () -> Void) { // function body goes here } // Here's how you call this function without using a trailing closure: someFunctionThatTakesAClosure(closure: { // closure's body goes here }) // Here's how you call this function with a trailing closure instead: someFunctionThatTakesAClosure() { // trailing closure's body goes here }

후행 클로저는 클로저 표현식이 길어서 한 줄이나 인라인으로 작성하기 힘들 때 유용하다.


아래 예시 코드를 살펴보자. 

let digitNames = [ 0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine" ] let numbers = [16, 58, 510]

배열의 map 메소드에 클로저를 전달하는 방식으로 numbers 배열을 가지고 String 배열을 만들 수 있다. 


let strings = numbers.map { (number) -> String in var number = number var output = "" repeat { output = digitNames[number % 10]! + output number /= 10 } while number > 0 return output } // strings is inferred to be of type [String] // its value is ["OneSix", "FiveEight", "FiveOneZero"]

map 메소드는 배열의 각 아이템에 한 번씩 적용된다.

매핑된 배열의 값에서 타입이 추론되기 때문에 클로저의 매개변수 타입인 number는 생략가능하다.


후행클로저가 여러개 있는 경우를 살펴보자.

func loadPicture(from server: Server, completion: (Picture) -> Void, onFailure: () -> Void) { if let picture = download("photo.jpg", from: server) { completion(picture) } else { onFailure() } }

loadPicture를 호출할 때, 2개의 클로저를 제공한다.


loadPicture(from: someServer) { picture in someView.currentPicture = picture } onFailure: { print("Couldn't download the next picture.") }

loadPicture 함수는 백그라운드에서 네트워크 작업을 진행한다.

네트워크 작업이 끝나면 두개의 completion handler 중 하나를 호출한다.

함수를 위의 예시처럼 작성하면 성공적으로 작업이 끝난 후 ui 업데이트하는 코드로와 실패하는 코드를 깔끔하게 분리할 수 있다.


값 포획(Capturing Values)

클로저는 정의되어있는 context에 있는 상수나 변수 값을 포획할 수 있다.

클로저는 상수나 변수를 정의한 원래의 영역이 더이상 존재하지 않더라도 상수나 변수의 값을 조작하거나 참조할 수 있다.

스위프트에서, 가장 간단한 방식의 값포획은 내장함수이다.

내장함수는 바깥 함수의 매개변수를 포획할 수 있고 바깥 함수에 정의된 상수나 변수를 포획할 수 있다.


아래 예시 코드를 살펴보자

func makeIncrementer(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer }

makeIncrementer 함수는 () -> Int 타입의 클로저를 반환한다.

내장함수인 incrementer()는 어떤 매개변수도 가지지 않지만 runningTotal과 amount를 참조한다.

값을 포획한다는 것은 runningTotal과 amount는 makeIncrementer 호출이 끝나더라도 사라지지 않는다는 것을 뜻한다.

그리고 incrementer이 호출될 때마다 runningTotal은 사용 가능하다는 것을 의미한다.


let incrementByTen = makeIncrementer(forIncrement: 10)

incrementByTen() // returns a value of 10 incrementByTen() // returns a value of 20 incrementByTen() // returns a value of 30

incrementByTen 은 runningTotal 변수를 10씩 더하는 incrementer 함수를 참조한다.


두번째 함수를 정의해보자.

아래 incrementBySeven은 새로운 runningTotal 변수를 만들어 참조한다.

let incrementBySeven = makeIncrementer(forIncrement: 7) incrementBySeven() // returns a value of 7


incrementByTen을 호출하는 것은 자신의 runningTotal 변수를 계속 증가시킨다.

그리고 incrementBySeven이 포획한 runningTotal에 영향을 끼치지 않는다.


클로저는 참조타입(Closures Are Reference Types)

위의 예시에서 incrementBySeven과 incrementByTen은 상수이다.

하지만 상수가 참조하는 클로저들은 여전히 runningTotal을 증가시킨다.

이것은 함수와 클로저가 참조타입이기 때문이다.


함수나 클로저를 상수나 변수에 할당할 때마다, 당신은 상수나 변수가 함수나 클로저를 참조할 수 있게 한다.

위의 예시에서 incrementByTen은 그것의 상수를 참조한다

이것은 두개의 다른 상수나 변수에 클로저를 할당한다면, 두개의 상수나 변수 모두 같은 클로저를 참조한다는 것을 말한다.

let alsoIncrementByTen = incrementByTen alsoIncrementByTen() // returns a value of 50 incrementByTen() // returns a value of 60


Escaping Closure.

클로저가 함수의 매개변수로 전달 되었지만 함수가 리턴된 이후에 불려질 때, 클로저가 함수를 escape 한다고 한다.

하나의 매개변수로 클로저를 취하는 함수를 선언할 때, 클로저가 escape하는 것을 허용하기위해 @escaping을 파라미터 타입 앞에 사용한다.

클로저가 escape할 수 있는 사례는 함수 밖에 정의된 변수에 저장되어있는 경우이다.

예를들어, 비동기실행을 하는 많은 함수들은 클로저를 completion handler의 형태로 매개변수로 사용한다.

함수는 실행 후에 return한다. 하지만 클로저는 실행이 완료될 때까지 호출되지않는다. - 클로저는 escape할 필요가 있다.

예를 들어보자.

var completionHandlers: [() -> Void] = [] func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) }

someFunctionWithEscapingClosure 함수는 매개변수로 클로저를 가진다. 그리고 함수 밖에 선언된 배열에 클로저를 더한다.

만약 함수에 @escaping 을 표시하지 않았다면, 컴파일에러를 만났을 것이다.

self를 참조하는 escaping closure는 self가 클래스 객체를 참조하는지 특별히 신경써야한다.

self를 capture하는 것은 strong reference cycle을 우연히 만들지도 모르기 때문이다. 

일반적으로, 클로저의 body에 변수들을 사용하면서 변수들을 capture한다.

하지만 이 경우에 명료하게해야할 부분이 있다.

만약 당신이 self를 capture한다면, 명료하게 self를 적거나 클로저의 capture list에 self를 포함해야한다.

self를 적는것은 개발자의 의도를 전달하고 개발자에게 reference cycle이 없다는 것을 상기시킨다.

예를들어, 아래 코드에서 someFunctionWithEscapingClosure에 전달된 클로저는 self를 명료하게 참조한다.

반대로, someFunctionWithNonescapingClosure에 전달된 클로저는 self를 암시적으로 참조하는 nonescaping closure이다.

func someFunctionWithNonescapingClosure(closure: () -> Void) { closure() } class SomeClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { self.x = 100 } someFunctionWithNonescapingClosure { x = 200 } } } let instance = SomeClass() instance.doSomething() print(instance.x) // Prints "200" completionHandlers.first?() print(instance.x) // Prints "100"


아래 예시는 self를 클로저의 capture list에 포함시켜서 capture하는 doSomething 버전이다. 그리고 self를 암시적으로 참조한다.

class SomeOtherClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { [self] in x = 100 } someFunctionWithNonescapingClosure { x = 200 } } }


만약 self가 구조체나 enum의 객체라면, self를 항상 암시적으로 참조할 수 있다. 그러나 escaping closure는 self에 대해 수정가능한 참조를 capture할 수 없다. 구조체와 enum은 수정을 허용하지 않는다.

struct SomeStruct { var x = 10 mutating func doSomething() { someFunctionWithNonescapingClosure { x = 200 } // Ok someFunctionWithEscapingClosure { x = 100 } // Error } 

} 

someFunctionWithEscapingClosure 함수의 호출은 에러이다.

왜냐하면 mutating function 안에 있기 때문이다.

이것은 escaping closure는 self에 대한 수정가능한 참조를 캡쳐할 수 없다는 것에 위반한다.


오토클로저 (Autoclosures)

autoclosure는 함수에 매개변수로 전달되는 표현식을 가지기위해 자동으로 만들어지는 클로저이다.

autoclosure는 어떤 매개변수도 가지지않고 호출되었을 때, 안에 가지고있는 표현식의 값을 Return한다.

이러한 문법적 편리함은 개발자가 explicit closure 대신에 일반적인 표현식을 사용함으로써 함수의 파라미터 주변에 {}를 생략하게 해준다.

autoclosure를 가지는 함수를 호출하는 것은 흔하다.  하지만 그런 함수를 구현하는 것은 흔하지 않다.

예를들어, assert(condition:message:file:line:) 함수는 condition과 message 파라미터로 autoclosure를 가진다. condition파라미터는 오직 디버그 빌드에서 실행되고 message 파라미터는 오직 condition 이 false일 때 실행된다.

autoclosure는 지연 실행도 가능케한다. 왜냐하면 안의 코드가 클로저를 호출할 때까지 실행되지 않기 때문이다.

지연실행(Delaying evaluation)은 부작용을 갖고있거나 계산비용이 비싼 코드에 유용하다.

왜냐하면 개발자가 그 코드가 언제 실행되는지 통제할 수 있기 때문이다.

아래 코드를 통해 delaying evaluation을 살펴보자.

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] print(customersInLine.count) // Prints "5" let customerProvider = { customersInLine.remove(at: 0) } print(customersInLine.count) // Prints "5" print("Now serving \(customerProvider())!") // Prints "Now serving Chris!" print(customersInLine.count) // Prints "4"

customersInLine 배열의 첫번째 요소가 클로저 안의 코드에의해 지워지지만, 배열의 첫 요소는 클로저가 실행될 때까지 지워지지 않는다.

만약 클로저가 절대 호출되지 않는다면 클로저 안의 표현식은 절대 실행되지 않는다. 이것은 배열의 첫 요소가 절대 지워지지 않는다는 것을 말한다.

customerProvicder는 String이 아니다. 하지만 어떤 매개변수도 없이 String을 반환하는 함수이다.


// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"] func serve(customer customerProvider: () -> String) { print("Now serving \(customerProvider())!") } serve(customer: { customersInLine.remove(at: 0) } ) // Prints "Now serving Alex!"

위의 serve(customer:) 함수는 손님의 이름을 리턴하는 explicit closure를 가진다.

아래의 serve함수는 같은 기능을 하지만 대신에 파라미터의 타입에 @autoclosure 속성을 표시하면서 autoclosure를 가진다.

이제, 개발자는 마치 serve함수가 클로저 대신에 String 매개변수를 가지는 것처럼 함수를 호출할 수 있다.

매개변수는 자동으로 closure로 변환된다. 왜냐하면 customerProvider 파라미터의 타입은 @autoclosure로 표시되어있기 때문이다.

// customersInLine is ["Ewa", "Barry", "Daniella"] func serve(customer customerProvider: @autoclosure () -> String) { print("Now serving \(customerProvider())!") } serve(customer: customersInLine.remove(at: 0)) // Prints "Now serving Ewa!"


만약 escaping이 가능한 autoclosure를 원한다면 @autoclosure와 @escaping 속성을 같이 사용한다.

var customerProviders: [() -> String] = [] func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) { customerProviders.append(customerProvider) } collectCustomerProviders(customersInLine.remove(at: 0)) collectCustomerProviders(customersInLine.remove(at: 0)) print("Collected \(customerProviders.count) closures.") // Prints "Collected 2 closures." for customerProvider in customerProviders { print("Now serving \(customerProvider())!") } // Prints "Now serving Barry!" // Prints "Now serving Daniella!"

위의 코드에서, customerProvider 매개변수로 전달된 closure를 호출하는 대신에, collectCustomerProvicers(_:) 함수는 customerProviders 배열에 클로저를 추가한다.

배열은 함수 영역 밖에 선언되어있다. 그래서 배열안에 closure들은 함수가 리턴된 이후에 실행될 수 있다.

그 결과, customerProvider 매개변수의 값은 반드시 escape이 허용되어야한다.







Share:
Read More