ios 코어 로케이션 프레임워크로 위치 정보 수집하기



1. 코어로케이션 매니져

코어 로케이션 프레임워크의 주요 클래스는 CLLocatoinManagerCLLocatoin 이다 

CLLocatoinManager 클래스의 인스턴스는 아래처럼 생성가능하다.


var locatoinManager : CLLocatoinManager = CLLocatoinManager()


CLLocatoinManager 인스턴스로 두 메서드 중 하나를 호출 할 수 있다


포그라운드에 있을 때만 위치정보를 추척한다면

locatoinManager.requestWhenInUseAuthorizatoin()


백그라운드에 있을 때도 위치추적을 하는 경우라면

locatoinManager.requestAlwaysAuthorizatoin()


2.위치정보 수집을 위한 허가 구하기

Info.plist 파일 내에 포함된 Information Property List 딕셔너리에 특정 값을 추가하는데 아래 키가 그 값들 중 하나다.


NSLocatoinWhenInUseUsageDescriptoin - 애플리케이션이 포그라운드에서 실행될 때 현재 위치를 접근해야 하는 이유를 사용자에게 묻는다.


NSLocatoinAlwaysUsageDescriptoin - 애플리케이션이 실행되는 동안 (백그라운드에서도) 현재 위치 데이터를 접근해야 하는 이유 설명


3.위치 정확도 설정

정확도의 단계는 CLLocatoinManager객체의 desiredAccuracy 속성을 통해 설정가능하다. 중요한점은 베터리를 많이 소모한다는 점!

이 속성을 설정하기 위해 정의된 상수 값은 아래와 같다


kCLLocatoinAccuracyBestForNavigation - 가장 높은 수준의 정확도, 외부 전원이 연결되어 있을 경우에만 사용


kCLLocatoinAccuracyBest - 베터리로 동작할 때 권장되는 가장 높은 수준의 정확도


kCLLocatoinAccuracyNearestTenMeters - 10미터 이내의 정확도


kCLLocatoinAccuracyHundredMeters - 100미터 이내의 정확도


kCLLocatoinAccuracyKilometer - 1키로미터 이내의 정확도


kCLLocatoinAccuracyThreeKilometers - 3키로미터 이내의 정확도


예)

locatoinManager.desiredAccuracy = kCLLocatoinAccuracyKilometer


4. 거리 필터 구성하기

로케이션 매니저가 위치 변화를 알려주는 기본 설정은 디바이스 위치가 변할 때마다 알려주는 것이다. 

로케이션 매니저의 distanceFilter 속성을 사용하면 어느 정도 거리의 위치 변화가 생겼을 때 앱이 알림을 받을지 말지를 설정할 수 있다.

예를 들면 distanceFilter가 1000미터로 설정되어 있으면 앱은 1000미터 이상의 위치 변화가 생겼을 때 알림을 받는다.


locatoinManager. distanceFilter = 1500.0


kCDistanceFilterNone 상수를 사용해서 distanceFilter 설정을 취소할 수 있다.


locatoinManager. distanceFilter = kCDistanceFilterNone


#로케이션 매니져 델리게이트

 로케이션 매니저는 CLLocationManagerDelegate 프로토콜에 정의된 두 개의 메서드를 사용하여 위치 정보 변경과 에러를 알려준다.

이 프로토콜을 따르기 위해 구현해야 할 두개의 메서드 템플릿은 아래와 같다.


    //위치가 변경될 때마다  이 메서드가 호출되며 가장 최근 위치 데이터를 배열의 마지막 객체에 포함하는 CLLocatoin 객체들의 배열이 인자로 전달된다.

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        // 위치 데이터 업데이트 처리

    }

    

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {

        //에러 처리

    }

    

    //앱의 위치 추적 허가 상태가 변경되면 이 메서드를 호출해서 알려준다.

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {

        // 사용자에 의해서, 더이상 앱이 위치 정보를 얻지 못하게 될 수 있다.

        // 현재의 상태를 체크하고 그에 맞는 처리를 실시한다.


}


어떤 클래스가 로케이션 매니저를 위하 델리게이트로 구성된다면 객체는 로케이션 매니저 인스턴스에 할당되어야 한다. 

대부분의 경우, 델리게이트는 로케이션 매니저가 있는 뷰 컨트롤러 자신이 될 것이다.


locatoinManager.delegate = self


#위치 업데이트 시작

위 처럼 설정되고 사용자로부터 접근 허가를 받았다면, 로케이션 매니저는 위치정보추적을 시작하도록 지시할 수 있다.

locatoinManager.startUpdatingLocation()


위치가 업데이트 될 때마다 로케이션 매니저에 의해 didUpdateLocations 델리게이트 메서드가 호출되고, 현재 위치에 대한 정보가 전달 된다.


#CLLocatoin 객체에서 위치 정보 얻기

위치 정보는 didUpdateLocation 델리게이트 메서드를 통해 CLLocation  객체의 형태로 전달된다. CLLocation 객체는 다음의 데이터를 포함하고 있다.


Latitude

Longtitude

Horizontal Accuracy

Altitude

Altitude Accuracy


경도와 위도

경도와 위도 값은 CLLocationDegrees의 형태로 저장되어 있으며, 다음과 같이 CLLocation 객체로 부터 얻어진다.

let currentLatitude : CLLocationDistance = location.coordinate.latitude

let currentLongtitue : CLLocationDistance = location.coordinate.longtitude


정확도 

수평, 수직 정확도는 CLLocationAccuracy에 미터 단위로 저장된다.

let verticalAccuracy : CLLocationAccuracy = location.verticalAccuracy

let horizontalAccuracy : CLLocationAccuracy = location.horizontalAccuracy


고도

해발고도 값은 CLLocationDistance에 미터 단위로 저장된다.

let altitude: CLLocationDistance = location.altitude


현재위치

로케이션 매니저에서 얻으려는 것이 연속적인 위치업데이트가 아니라 사용자의 현재 위치라면 로케이션 매니저 인스턴스의 requestLocation 메서드를 호출하여 얻을 수 있다.

이 메서드는 현재 위치를 식별하여 현재 위치 정보를 전달하며, didUpdateLocations 델리게이트 메서드를 한 번 호출 한다. 그런다음, 위치 업데이트는 자동으로 꺼진다.

locationManager.requestLocation()


거리계산하기

두 CLLocation 지점 간의 거리는 끝 지점의 distanceFromLocation 메서드에 시작 지점을 인자로 넘겨주어 계산할 수 있다. startLocation과 endLocation 간의 거리를 계산한다.

var distance: CLLocationDistance = endLocation.distanceFromLocation(startLocation)


애플리케이션이 백그라운드 상태에서 위치 정보의 변화를 업데이트 받아야 한다면 applicationDidEnterBackground 메서드에서 로케이션 매니저 객체의 startMonitoringSignificantLocationChanges 메서드를 호출하여 정확도를 낮춰야 베터리 소모를 줄일 수 있다.



ios 코어 로케이션 프레임워크 예제


스토리보트 화면

viewcontroller.swift


import UIKit

import CoreLocation


class ViewController: UIViewController, CLLocationManagerDelegate {

    

    @IBOutlet weak var latitude: UILabel!

    @IBOutlet weak var longitude: UILabel!

    @IBOutlet weak var horizontalAccuracy: UILabel!

    @IBOutlet weak var altitude: UILabel!

    @IBOutlet weak var verticalAccuracy: UILabel!

    @IBOutlet weak var distance: UILabel!

    

    //사용자에게 허가를 구하기 위한 인스턴스

    

    var locationManager: CLLocationManager = CLLocationManager()

    var startLocation: CLLocation!

    

    override func viewDidLoad() {

        super.viewDidLoad()

        locationManager.desiredAccuracy = kCLLocationAccuracyBest //최고의 정확도 설정

        locationManager.delegate = self                         //뷰 컨트롤러를 로케이션 매니저 객체에 대한 앱델리게이트로 선언

        locationManager.requestWhenInUseAuthorization()         // 위치 정보를 추적하기 위한 허가 요청

        locationManager.startUpdatingLocation()                 // 시작

        startLocation = nil

        

    }

    

    @IBAction func resetDistance(_ sender: AnyObject) {

        startLocation = nil

    }

    

    //델리게이트 메서드 구현하기

    //로케이션 매니저가 위치 변화를 인식하면 이 메서드를 호출한다.

    //이 메서드가 호출될때 최근 업데이트 내용을 담고 있는 위치 객체의 배열이 전달된다.

    //가장 최근의 위치 정보를 나타내는 항목은 배열의 마지막 항목이다.

    //이 메서드 시작 -> 배열에서 마지막 위치 객체 추출 -> 그 객체에 포함된 데이터가지고 작업

    func locationManager(_ manager: CLLocationManager,

                         didUpdateLocations locations: [CLLocation])

    {

        let latestLocation: CLLocation = locations[locations.count - 1]

        

        latitude.text = String(format: "%.4f",

                               latestLocation.coordinate.latitude)

        longitude.text = String(format: "%.4f",

                                latestLocation.coordinate.longitude)

        horizontalAccuracy.text = String(format: "%.4f",

                                         latestLocation.horizontalAccuracy)

        altitude.text = String(format: "%.4f",

                               latestLocation.altitude)

        verticalAccuracy.text = String(format: "%.4f",

                                       latestLocation.verticalAccuracy)

        

        if startLocation == nil {

            startLocation = latestLocation

        }

        

        let distanceBetween: CLLocationDistance =

            latestLocation.distance(from: startLocation)

        

        distance.text = String(format: "%.2f", distanceBetween)

    }

    

    func locationManager(_ manager: CLLocationManager,

                         didFailWithError error: Error) {

        //에러 발생

    }


}


결과 


참고로직

    //지도 객체

    var myMap: MKMapView = {

        let map = MKMapView()

        map.translatesAutoresizingMaskIntoConstraints = false

        return map

    }()

    

    //코어 로케이션 프레임워크의 주요 클래스는 CLLocatoinManager 와 CLLocatoin 이다

    //CLLocatoinManager 클래스의 인스턴스는 아래처럼 생성가능하다.

    let locationManager = CLLocationManager()   //지도 매니저 객체

    

    @objc func handleBack(){

        dismiss(animated: true, completion: nil)

    }

    

    override func viewDidLoad() {

        super.viewDidLoad()

        

        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "뒤로", style: .plain, target:self , action: #selector(handleBack))

        

        view.addSubview(myMap)

        

        setMyMapLayout()



        locationManager.delegate = self //locationManager의 델리게이트를 self로 설정

        locationManager.desiredAccuracy = kCLLocationAccuracyBest //정확도를 최고로 설정

        locationManager.requestWhenInUseAuthorization() //위치데이터를 추적하기 위해서 사용자에게 승인 요구

        locationManager.requestAlwaysAuthorization() //백그라운드에서도 사용하겠다 사용 요구

        locationManager.distanceFilter = 1000 // 1000미터 이상의 위치 변화가 생겼을 때 알림을 받는다.

        locationManager.startUpdatingLocation()     // 위치 업데이트 시작

        myMap.showsUserLocation = true              // 위치 보기 값을 true로 설정

        

    }




mkMap을 이용해서 길찾기 기능 구현



스토리 보드로 뷰를 이런식으로 간단하게 만들고




import UIKit

import Contacts

import MapKit


class ViewController: UIViewController {


    

    //1.스토리 보드에서 만든 객체들을 컨트롤러와 연결시켜주었다.

    @IBOutlet weak var address: UITextField!

    @IBOutlet weak var city: UITextField!

    @IBOutlet weak var state: UITextField!

    @IBOutlet weak var zip: UITextField!

    

    

    //2. 사용자가 주소를 입력하고 getDirections 버튼을 누르면 해당메서드는 필드들에 있는 주소 정보를 추출한다.

     /*이렇게 하는 목적은 입력한 위치를 가진 MKPlacemark 객체를 생성하기 위한 것이다.

     MKPlacemark 인스턴스는 인스턴스화 되기 전에 주소에 대한 위도와 경도가 필요하다.

     getDirectoin 메서드의 첫번째 단계는 주소에 대한 순방향 지오코드 변환을 수행하는 것이다.

     이렇게 하기전에 계산된 좌표들을 저장할 속성 하나를 이곳에 선언해야 한다.

     따라서 CoreLocation 프레임 워크를 임포트 해야 한다.*/

    

    var coords: CLLocationCoordinate2D?

    

    override func viewDidLoad() {

        super.viewDidLoad()

    }


    @IBAction func getDirections(_ sender: AnyObject) {

        

        //One Infinite Loop, Cupertino, CA 95014

        let addressString = "\(address.text) \(city.text) \(state.text) \(zip.text)"

        

        //3.지오코딩 변환을 수행하는 소스 코드

        CLGeocoder().geocodeAddressString(addressString, completionHandler:

            {(placemarks, error) in


            if error != nil {

                print("에러 발생: \(error!.localizedDescription)")

            } else if placemarks!.count > 0 {

                let placemark = placemarks![0]

                let location = placemark.location

                self.coords = location!.coordinate

                //4.showMap을 호출 한다.

                self.showMap()

            }

        })


    }

    

    //5. 이 메서드는 목저지 주소에 대한 새로운 MKPlacemark 인스턴스를 생성하고, 거기에 드라이브 길찾기를 위한 옵션들을 설정한다음 지도를 실행한다.

    

    

    func showMap() {

        

        //텍스트 주소를 딕셔너리 형태로 저장

        let addressDict =

               [CNPostalAddressStreetKey: address.text!,

                CNPostalAddressCityKey: city.text!,

                CNPostalAddressStateKey: state.text!,

                CNPostalAddressPostalCodeKey: zip.text!]


        //주소 딕셔너리와 좌표를 이용해서 MKPlacemark 인스턴스 생성

        let place = MKPlacemark(coordinate: coords!,

                                 addressDictionary: addressDict)


        let mapItem = MKMapItem(placemark: place)


        //옵션 설정

        let options = [MKLaunchOptionsDirectionsModeKey:

                            MKLaunchOptionsDirectionsModeDriving]


        mapItem.openInMaps(launchOptions: options)

    }



}


ios 지도 mkmap 3


이번에는 mkplacemark 와 mkmapitem을 이용해서 텍스트 주소에 해당하는 표시를 지도위에 해보겠다


1. mkplacemark 인스턴스 생성하기


  mkmapitem 클래스를 이용해서 지도를 표시할  나타나는 각각의 위치는 mkplacemark 객체에 의해 표현되어야 한다.


  mkplacemark 객체를 생성할   객체는 주소 속성 정보를 포함하고 있는 nsdictionary 객체와 함께 위치의 지리적 좌표로 초기화 되어야 한다.


        


        //매게 변수로 위도,경도가 들어간다.


        let coords = CLLocationCoordinate2DMake(51.5083, -0.1384)


        


        let address = [CNPostalAddressStreetKey: "181 Piccadilly, St. James's", CNPostalAddressCityKey: "London", CNPostalAddressPostalCodeKey: "W1A 1ER", CNPostalAddressISOCountryCodeKey: "GB"]


        


        //주소 딕셔너리에 nil 값을 전달하여 mkplacemark 객체를 초기화   있다그러나  결과로 지도가 나타날때 현재 위치는 표시되겠지만주소 대신에 unknown이라는 태그가 붙는다.


        //mkplacemark 객체를 생성할때 좌표는 필수 값이다.


        //위치 좌표가 아닌 텍스트 주소를 알고 있는 경우 mkplacemark 인스턴스를 생성하기 전에 좌표를 얻기 위하여 지오코딩이 사용되어야 한다.


        let place = MKPlacemark(coordinate: coords, addressDictionary: address)


        


        // mkmapitem으로 작업


        // mkplacemark 객체를 인자로 전달해서 초기화


        let mapItem = MKMapItem(placemark: place)


 


        //적절한 표시로 목적지를 가리키는 지도를 연다


        mapItem.openInMaps(launchOptions: nil)


결과화면



        //mapItemForCurrentLocation 메서드 호출을 통해 사용자 디바이스의 현재 위치를 표시 수도 있다.

        

        let mapItem2 = MKMapItem.forCurrentLocation()       //현재위치

        

        let mapItems = [mapItem, mapItem2]              //미국위치 하나 표시, 현재 위치 하나 표시

        

        MKMapItem.openMaps(with: mapItems, launchOptions: nil)   //열기


결과 : 오른쪽에 파란점이 나의 위치고, 왼쪽에 빨간색 표지판이 위에 코드로 표시한 미국의 위치다.







#mkmapitem 옵션


1.MKLaunchOptionsDirectionsModeKey 

턴 방식 길찾기를 지도에 제공할지를 제어한다. 단 하나의 placemaker객체만 존재할 경우는 현재 위치에서 그곳까지의 방향이 제공된다. 여러가지 모드가 있는데, MKLaunchOptionsDirectionsModeDriving, MKLaunchOptionsDirectionsModeWalking,MKLaunchOptionsDirectionsModeTransit이 있다.


2.MKLaunchOptionsMapTypeKey 

지도가 표준, 위성, 하이브리드, 플라이오버, 또는 하이브리드 플라이오버 지도 이미지를 표시해야 하는지 가리킨다.


3.MKLaunchOptionsModeCenterKey

지도의 중앙이 되는 위치에 대한 좌표를 포함하는 CLLocationCoordinate2D 구조체 값에 해당한다.


4.MKLaunchOptionsMapSpanKey

실행되면 지도에 표시되어야 하는 영역을 지정하는 MKCoordinateSpan 구조체 값이다.


5.MKLaunchOptionsShowsTrafficKey 

지도가 실행될 때 교통정보를 지도 위에 겹치도록 할 것인지를 가리키는 불리언 값이다.


6.MKLaunchOptionsCameraKey

3D 플라이 오버 모드에서 지도가 표시될 때, 이 키에 할당된 값은 특정 시점에서 지도를 보도록 구성된 MKMapCamera 객체의 형태를 취한다. 


예)


        let options = [MKLaunchOptionsDirectionsModeKey:

            MKLaunchOptionsDirectionsModeDriving, MKLaunchOptionsShowsTrafficKey: true] as [String : Any]

        

        MKMapItem.openMaps(with: mapItems, launchOptions: options)


결과화면


이런식으로 나온다.

또 MKMapItem 클래스에 name, url 등의 속성을 이용하면 지도에 부가적인 정보를 넣을 수 있다.



        let mapItem = MKMapItem(placemark: place)

 

        mapItem.name = "empire state building"

        mapItem.phoneNumber = "1010234"

        mapItem.url = NSURL(string: "http://www.naver.com") as! URL

        mapItem.openInMaps(launchOptions: nil)




지오코딩과 MKPlacemark 와 MKMapItem 클래스를 이용하면 간단한 코드로도 다양한 지도 어플리케이션을 만들 수 있다.







ios 지도 mkmap 2



위도와 경로 좌표를 이용해서 해당 주소 데이터를 찾는 방법을 알아본다. 먼저 앞의 포스팅에는 주소를 이용해서  미국 캘리포니아에 있는 애플의 위도와 경도를 구했다. 그 데이터를 이용해서 이번엔 주소를 찾아본다. 코드는 아래와 같다. 간단하다.


            //역방향 지오코딩

            

            let geoCoder = CLGeocoder()

            //cllocatoin객체는 위도와 경로 좌표로 초기화

            let newLocation = CLLocation(latitude: 37.3316833, longitude: -122.0301031)

            

            //geoCoder에 reverseGeocodeLocation 메서드로 전달 된다.

            geoCoder.reverseGeocodeLocation(newLocation, completionHandler: { (placemarks, error) in

                if error != nil {

                    print("에러 발생 \(error!.localizedDescription)")

                }

                //값이 있으면 배열 값으로 반환

                if placemarks!.count > 0 {

                    let placemark = placemarks![0]

                    //딕셔너리 값으로 반환

                    let addressDictionary = placemark.addressDictionary

                    

                    //key 값을 이용해서 주소 찾기

                    let address = addressDictionary!["Street"]

                    let city = addressDictionary!["City"]

                    let state = addressDictionary!["State"]

                    let zip = addressDictionary!["ZIP"]

                    

                    print("\(address!) \(city!) \(state!) \(zip!)")

                }            

})


결과 값은 아래와 같다.

1 Infinite Loop 쿠퍼티노 CA 95014


참고!

실제로 지오코딩은 ios 디바이스에서 실행되지 않고 변환이 필요할 때 마다 디바이스가 연결된 서버에서 실행이 된다.

변환이 끝나면 그 결과가 순차적으로 반환된다. 그래서 지오코딩은 인터넷에 연결되어 있을 때만 장소를 가져올 수 있다.




ios 지도 mkmap 1



텍스트 기반의 주소를 그 주소에 해당하는 좌표를 가지는 CLLocation 객체로 변환하는 CLGeocoder 클래스를 이용해서 미국 캘리포니아의 애플사의  좌표를 가져오는 예제를 살펴본다.




        let addressString = "One Infinite Loop, Cupertino, CA 95014"

        CLGeocoder().geocodeAddressString(addressString) { (placemarks, error) in

            

            if error != nil{

                print("에러발생 \(error!.localizedDescription)")

            }else if placemarks!.count > 0{

                let placemark = placemarks![0]

                let location = placemark.location

                let coords = location!.coordinate

                

                print("위도 : \(coords.latitude)")

                print("경도 : \(coords.longitude)")

                

            }



위의 코드를 실행하면 

위도 : 37.3316833

경도 : -122.0301031


각종 변수들을 찍어보면

    




            

placemark : 1 Infinite Loop, 1 Infinite Loop, 쿠쿠\355\215퍼티쿠\355\215퍼티\353\205노, CA  95014, 미 합중국 @ <+37.33168330,-122.03010310> +/- 100.00m, region CLCircularRegion (identifier:'<+37.33169175,-122.03021900> radius 62.40', center:<+37.33169175,-122.03021900>, radius:62.40m)

                




location : Optional(<+37.33168330,-122.03010310> +/- 100.00m (speed -1.00 mps / course -1.00) @ 16/05/2018, 15:05:16 Korean Standard Time)

                




coords : CLLocationCoordinate2D(latitude: 37.331683300000002, longitude: -122.03010310000001)







이같은 결과가 나온다.


이 코드는 간단하게 주소를 담은 문자열을 전달해서 CLGeocoder 인스턴스의 geocodeAddressString 메서드를 호출하고 변환이 끝나면 완료 핸들러를 호출 한다.

위치좌표 개수는 placemarks 배열 에 담아 반환되는데 하나 이상이면 위치좌표를 얻었다. 위의 코드에서는 위치좌표가 한 개라고 가정하고 배열 0 번째 방에 데이터를 가져와서 뿌려줬다. 이러한 방식을 어려운 말로 순방향 지오코딩이라고 한다.  실제로 위에서 얻은 좌표를 확인해 보니 캘리포니아에 있는 애플 회사가 나왔다.












Ios AutoLayout 7 - AutoLayout Landscape 이슈

● 들어가기 전

이번 포스팅에서는 Landscape을 했을때 발생하는 AutoLayout 이슈에 대해서 살펴보자. 글로 쓰는 것보다 사진을 바로 보자.

대충 이런 현상이다. 세로였을때 바로 나오다가  Landscape했을 때 위와 같이 두 페이지가 반반 씩 동시에 나오는 이슈가 발생한다. viewWillTransition 함수를 override 해서 해결해보자.

● 실습순서

1.viewWillTransition 함수 override
2.소스코드 리팩토링

● viewWillTransition 함수

아래의 viewWillTransitoin 함수는 view가 바뀔때 작동되는 함수다. 아래의 코드를 SwipingController 제일 윗 부분에 작성한다.  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        
        coordinator.animate(alongsideTransition: { (_) in
            //원래의 collectionViewLayout 기능을 정지 시킨다.
            self.collectionViewLayout.invalidateLayout()
            //페이지 컨트롤의 현재페이지가 0 이면
            if self.pageControl.currentPage == 0 {
                //오프셋을 .zero 로 설정
                self.collectionView?.contentOffset = .zero
            } else {
                //아니라면 넘겨지는 화면에 맞추기
                let indexPath = IndexPath(item: self.pageControl.currentPage, section: 0)
                self.collectionView?.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
            }
            
        }) { (_) in
            
        }
    }
cs

● 소스코드 리팩토링

이제 모든 소스코드 작성이 끝났다. 마지막으로 SwipingController 안에 있는 소스코드를 정리해주도록 하겠다. SwipingController 안에는 UICollectionView를 구현하는 함수도 있고 위에 함수처럼 SwipingController 클래스를 확장시킨 함수도 있다. 먼저 SwipingController+UICollectionView.swift 파일을 만들어서 UICollectionView를 구현하는 함수만 따로 분리 시키자.

SwipingController+UICollectionView.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 
import UIKit
 
extension SwipingController{
    
    //컬렉션 뷰의 셀 사이사이마다 간격설정 원래는 디폴드 값으로 10이 지정되어 있음
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int-> CGFloat {
        return 0
    }
    //컬렉션 뷰의 cell 개수 지정.
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int-> Int {
        return pages.count
    }
    //컬렉션 뷰의 cell을 구성하는 함수
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        //위에서 등록해준 커스텀 셀을 가져와서 리턴해줌
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId"for: indexPath) as! PageCell
        let page = pages[indexPath.item]
        //view에 page 변수에 할당
        cell.page = page
        return cell
    }
    //커스텀 셀의 크기를 view에 꽉차게 해주었다.
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: view.frame.width, height: view.frame.height)
    }
    
}
 
cs

마지막으로 SwipingController+extension.swift 파일을 만들어서 제일위  Landscape 이슈와 관련된 함수를 분리 시켜 주자. 그리고 SwipingController에서 pageControl의 접근 제어자 private을 삭제해주자. 그래야 SwipingController+extension.swift 파일 viewWillTransition 함수 내에서도 참조할 수 있다.

SwipingController+extension.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import UIKit
 
extension SwipingController {
    
    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        
        coordinator.animate(alongsideTransition: { (_) in
            //원래의 collectionViewLayout 기능을 정지 시킨다.
            self.collectionViewLayout.invalidateLayout()
            //페이지 컨트롤의 현재페이지가 0 이면
            if self.pageControl.currentPage == 0 {
                //오프셋을 .zero 로 설정
                self.collectionView?.contentOffset = .zero
            } else {
                //아니라면 넘겨지는 화면에 맞추기
                let indexPath = IndexPath(item: self.pageControl.currentPage, section: 0)
                self.collectionView?.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
            }
            
        }) { (_) in
            
        }
    }
    
}
 
 
cs

● 파일

최종 파일은 아래와 같다. 따로 폴더를 만들어서 MVC패턴으로 더 깔끔하게 정리해줘도 될것 같다.

● 실행

마지막으로 실행을 해보면 이제  AutoLayout Landscape 이슈가 해결된 것을 볼 수 있다.

● 마무리

Ios AutoLayout 포스팅을 하면서 UIPageControl, button 이벤트, UICollectionView, AutoLayout 함수, 계층 구조를 이용해서 부모View에 자식View 넣기, UIStackView가 배열 타입이라는 것, MVC 패턴, extension 개념 등등 ios의 다양한 지식과 기술을 익힌것 같다. 이것들을 더 응용해서 새로운 ios 앱을 만들어 보면 실력향상에 더욱 좋을 것 같다. 감사합니다.  





Ios AutoLayout 6 - UIButton 이벤트와 UIPageControl속성


● 들어가기 전

이번 포스팅에서는 이전 포스팅에서 만들었던 UIButton 이벤트를 걸어서 "이전버튼" 누르면 이전페이지로 이동시키고, "다음버튼" 누르면 다음 버튼으로 이동시키는 코드를 작성해보자그리고 페이지가 이동할때 UIPageControl 현재페이지를 보여주는 색깔점 표시도 변경시켜보자.

● 실습순서

1. ViewController.swift에 있는 UIButton객체와 UIPageControl객체와 autolayout을 설정한 함수 가져오기
2. 버튼에 이벤트 걸어주기
3.pageControl 설정
4.scrollViewWillEndDragging 함수 구현

● ViewController에 있는 객체들과  autolayout 설정 함수 가져오기

ViewController.swift 있는 UIButton객체와 UIPageControl객체와 autolayout 설정한 함수를 SwipingController.swift로 가져온다. 객체는 pages 변수 아래에 두고, setupBottomControls()함수호출은 viewdidLoad() 함수안에서 해주고 setupBottomControls(){...} 는 제일 밑에다가 위치시킨다. 맨 아래 전체 소스 코드 참고.


● 버튼에 이벤트 걸어주기

버튼객체 속성에 addTarget(self, action: #selector(handeNext), for: .touchUpInside) 함수를 이용해서 이벤트를 걸어준다. 버튼이 눌리면 handeNext 함수가 호출이 되고 그 내부에 있는 로직이 실행된다. 안에 로직은 현재페이지와 전체페이지 수를 이용해서 다음 페이지를 호출시커거나 이전 페이지를 호출시키는 로직이다. 간단하게 주석을 달아 놓았으니 print() 함수를 이용해서 찍어보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
 //이전 버튼
    private let preButton :UIButton = {
        let preBtn = UIButton(type:.system)
        preBtn.setTitle("이전"for: .normal)
        preBtn.translatesAutoresizingMaskIntoConstraints = false
        preBtn.setTitleColor(.gray, for: .normal)
        preBtn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
        preBtn.addTarget(self, action: #selector(handlePre), for: .touchUpInside)
        return preBtn
        
    }()
    @objc func handlePre(){
        print("aa")
        //print(pageControl.currentPage+1)//1
        //print(pages.count-1)//2
        
        let nextIndex = max(pageControl.currentPage - 10//1
        let indexPath = IndexPath(item: nextIndex, section: 0//[0,1]
        print(nextIndex) //1 , 0
        print(indexPath)// [0,1] [0,0]
        pageControl.currentPage = nextIndex //1
        collectionView?.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
        //print(nextIndex)
    }
    
    //다음 버튼
    private let nextButton :UIButton = {
        let nxtBtn = UIButton(type:.system)
        nxtBtn.setTitle("다음"for: .normal)
        nxtBtn.translatesAutoresizingMaskIntoConstraints = false
        nxtBtn.setTitleColor(.mainPink, for: .normal)
        nxtBtn.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
        nxtBtn.addTarget(self, action: #selector(handeNext), for: .touchUpInside) //터치가 이루어졌을때 handeNext 함수 호출
        return nxtBtn
    }()
    
   @objc func handeNext(){
        print("next")
        //print(pageControl.currentPage+1)//1
        //print(pages.count-1)//2
        //다음페이지 번호 구하기 - min 제일 작은값을 가져온다. 참고로 currentPage는 0 인 상태다
        let nextIndex = min(pageControl.currentPage + 1, pages.count - 1//1
        let indexPath = IndexPath(item: nextIndex, section: 0//[0,1]
        print(nextIndex)
        print(indexPath)
        pageControl.currentPage = nextIndex //1
        collectionView?.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
        //print(nextIndex)
    }
    
    //페이지 컨트롤러
    private lazy var pageControl: UIPageControl = {
        let pc = UIPageControl()
        pc.translatesAutoresizingMaskIntoConstraints = false
        pc.currentPage = 0
        //let a = pages.count
        pc.numberOfPages = pages.count
        pc.currentPageIndicatorTintColor = .mainPink
        pc.pageIndicatorTintColor = UIColor(red: 249/255, green: 207/255, blue: 224/255, alpha: 1)
        return pc
    }()
cs

● PageControl 속성 설정

그리고 pageControl객체안에서 numberOfPages 속성을 pages.count를 이용해서 동적으로 바꾸어준다. 클로저 안에서 위 배열을 참조하려면 클로저 변수 앞에 lazy var 를 붙여줘야 한다.(중요)


1
2
3
4
5
6
7
8
9
10
11
    //페이지 컨트롤러
    private lazy var pageControl: UIPageControl = {
        let pc = UIPageControl()
        pc.translatesAutoresizingMaskIntoConstraints = false
        pc.currentPage = 0
        //let a = pages.count
        pc.numberOfPages = pages.count
        pc.currentPageIndicatorTintColor = .mainPink
        pc.pageIndicatorTintColor = UIColor(red: 249/255, green: 207/255, blue: 224/255, alpha: 1)
        return pc
    }()
cs

● scrollViewWillEndDragging 함수 구현

이 함수는 cell을 손가락으로 드래그 한 후 끝났을때 위치를 인자 값으로 넘겨준다. 

이때 targetContentOffset.pointee.x 값을 활용해서 pageControl.currentPage의 값을 실시간으로 바뀌게 구해준다.

1
2
3
4
5
6
//오른쪽으로 스크롤 했을 때 - 위치로 현재 페이지 구해주기
    override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        let x = targetContentOffset.pointee.x
        pageControl.currentPage = Int(x / view.frame.width)
        
    }
cs

● 마무리

이번 포스팅에서는 UIButton 이벤트와 UIPageControl속성에 대해서 간단하게 알아보았다. UIButton 이벤트를 구현할때 반드시 호출되는 함수 앞에 @objc를 붙여주는 것을 잊지말자. 다음은 Ios AutoLayout의 마지막 포스팅으로 아이폰을 엎으로 눕혔을때 발생하는 이슈를 처리하는 법을 알아보도록 하자.




Ios AutoLayout 4 -  MVC 패턴 

● 들어가기 전

이번 포스팅에서는 mvc 패턴에 대해서 살펴보겠다. mvc 패턴은 아주아주 간단하게 말해서 어플리케이션을 만들때, m 모델단, v 화면단, c 컨트롤단으로 구분지어서 소스코드를 제작하는 방법이다. 반드시는 아니지만 굳이 구분을 하자면 프론트 엔드 즉, 디자이너는 v 부분 소스코드를 짜고, 개발자는 c 부분 소스코드를 짜고, db모델러는 m 부분을 담당한다.(쉽게 설명해서) 이러는 이유는 앞에서도 설명했는데, 페이지 하나에 화면소스, 컨트롤소스, 모델 소스를 짜버리면 나중에 유지보수하기가 힘들고 개발할때도 복잡해서 효율성이 떨어지기 때문이다. 아주 간단하게 설명하면 이렇다. 그렇다면 ios에서 mvc가 어떻게 활용되는지 보자. 

● 실습순서

1.모델객체 만들기

2.모델 객체를 컨트롤 부분에서 참조

3.cell을 구성하는 함수에서 모델 객체 데이터를 view로 할당해줌

4.view에서는 감지했다가 받은 데이터를 화면에 뿌려준다.

● 모델 객체 만들기

파일을 하나 만들어서 모델 객체를 하나 만들어 준다. 모델 객체는 struct 타입으로 만들어 준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
import Foundation
 
struct Page {
    //이미지 데이터 변수
    let imageName: String
    //글 제목 변수
    let headerText: String
    //글 본문 변수
    let bodyText: String
    
}
 
 
 
 
cs

● 컨트롤 부분에서 모델 객체 참조

SwipingController러 class 위에 모델객체에 값을 넣어준다. 사진과 사진제목은 각자 알아서 넣어 주도록 하자. 
1
2
3
4
5
6
    let pages = [
        Page(imageName: "mypic1", headerText: "ios 프로그래밍 입문 레슨", bodyText: "초보자도, 할머니도, 할아버지도 원숭이도 하는 코딩공부~ 글보다는 직접 쳐보면서 만들어보는게 훨씬 이해도 쉽고 효율적이에요~ 같이 열공해용~!"),
        Page(imageName: "mypic2", headerText: "안드로이드 프로그래밍 입문 레슨", bodyText: "세계에서 가장 많이 사용하는 os 안드로이드~! 프로그래밍 입문자는 java에 대한 개념부터 다지고 바로 앱으로 고고씽~!"),
        Page(imageName: "mypic3", headerText: "php 프로그래밍 쉽게 알려줌", bodyText: "나는 웹도 앱도 먼지 모르겠다. 일단 운영이되는 사이트를 바로 만들어 보고싶다. 그런분들께 쉽지만 아주 강력한 php를 추천합니다~!")
    ]
    
cs

다음 cell 구성을 담당하는 함수 내부에서 view에 데이터를 뿌려준다. 그리고 위쪽에 컬렉션뷰의 cell 개수를 담당해주는 함수에서 return 값을 4에서 pages.count 로 변경해준다. 이건 고정적으로 정해주기보다는 동적으로 정해주는것이 효율적이다.

1
2
3
4
5
6
7
8
9
10
11
12
    //cell을 구성하는 함수
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        //as! PageCell로 케스팅해준다.
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId"for: indexPath) as! PageCell
        //위에서 정의 한 배열의 값(모델객체값)을 각각 하나씩 빼서
        let page = pages[indexPath.item]
        //view에 page 변수에 할당
        cell.page = page
 
        return cell
    }
cs

● view에서 받은 객체모델값 뿌려주기

view단에서 컨트롤러에서 넘어올 모델객체 데이터를 받을 변수를 선언해 준다. 그리고 didSet으로 변수에 값이 할당되면 즉시 밑에서 만들었던 이미지 객체, 텍스트 객체 속성에 모델객체에 담겨진 값을 담아준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 //컨트롤에서 넘겨줄 모델 데이터 받을 변수
    var page: Page? {
        didSet {
            
            //언래핑:널값을 안전하게 처리하기 위해서 언래핑 해준다.
            guard let unwrappedPage = page else { return }
            //위에서 언래핑한 객체모델.변수 이름으로 데이터 view에 뿌려주기
            myImageView.image = UIImage(named: unwrappedPage.imageName)
            //마찬가지
            let attributedText = NSMutableAttributedString(string: unwrappedPage.headerText, 
            attributes: [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 18)])
            
            attributedText.append(NSAttributedString(string: "\n\n\n\(unwrappedPage.bodyText)"
            attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 13), NSAttributedStringKey.foregroundColor: UIColor.gray]))
            
            myTextView.attributedText = attributedText
            myTextView.textAlignment = .center
        }
    }
cs

● 실행

이제 어플리케이션을 실행해보자. controller에서 모델객체를 참조하고 값을 넣어주었다. 그 값들이 정확히 view에 뿌려지는지 확인해보자.


▲ 실행결과 모두다 잘 나온다.

● 마무리

이번 포스팅에서는 완벽한 MVC 패턴에 대해서 살펴보았다. 물론 아주완벽한 MVC 패턴은아니다. 일단은 view(여기서는 커스텀 셀), controller(여기서는 SwipingController), model(여기서Page) 이렇게 나누어 주었다는것만 기억하고 다음 포스팅으로 넘어 가겠다.





Ios AutoLayout 4 -UICollectionViewController 활용


● 들어가기 전

이번포스팅에서는 UICollectionViewController를 이용해서 페이지를 좌우로 넘겨주는 기능을 살펴보자.

● 실습순서

1.rootview를 UICollectionViewController를 상속받는 클래스로 지정해주기

2. UICollectionViewController를 상속받는 클래스 지정

3. UICollectionViewController의 Cell을 꾸며줄 Customcell 만들기

4.ViewController에 있던 소스 UICollectionViewController를 상속받는 클래스로 옮기기


● rootview 지정해주기

AppDelegate파일로 들어와서 window 객체를 초기화 하는 코드를 작성해준다. 각 코드에 대한 설명은 주석을 달아놓았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        
       //window객체 초기화
        window = UIWindow()
        //window객체를 code로 변경시켜주도록 설정
        window?.makeKeyAndVisible()
        
        //UICollectionViewFlowLayout 컬렉션 뷰 레이아웃과 관련된 객체
        let layout = UICollectionViewFlowLayout()
        //가로로 스크롤되게 설정
        layout.scrollDirection = .horizontal
        //SwipingController 객체를 생성하고 최상위 뷰로 설정
        let swipingController = SwipingController(collectionViewLayout: layout)
        
        window?.rootViewController = swipingController
        
        return true
    }
 
cs

● UICollectionViewController를 상속받는 클래스 지정

UICollectionViewController를 상속받는 클래스를 만들어 주었다.
여기서 UICollectionViewDelegateFlowLayout 도 상속받았는데, 레이아웃을 설정해 주려면 반드시 상속받아야 하는 녀석이다. 그리고 viewDidLoad()에서 배경화면을 설정해주고, customcell을 등록시켜주었다. 그 아래에 나열되있는 함수는 UICollectionViewController를 구성해주는 함수다. 반드시 구현을 해야 한다. UICollectionViewCell을 상속받는 PageCell은 따로 만들어준다. 나머지 설명은 주석을 참고하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 
import UIKit
//UICollectionViewController를 상속 받는 클래스, 레이아웃과 cell의 size를 조정하려면 UICollectionViewDelegateFlowLayout을
//상속받아야 한다.
class SwipingController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //배경색을 흰색
        collectionView?.backgroundColor = .white
        //collectionView에 cell을 등록해주는 작업, 여기서는 직접 만든 cell을 넣어주었고, 아이디를 설정해 주었다.
        collectionView?.register(PageCell.self, forCellWithReuseIdentifier: "cellId")
        //페이징기능 허용
        collectionView?.isPagingEnabled = true
    }
    //컬렉션 뷰의 셀 사이사이마다 간격설정 원래는 디폴드 값으로 10이 지정되어 있음
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int-> CGFloat {
        return 0
    }
    //컬렉션 뷰의 cell 개수 지정.
    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int-> Int {
        return 4
    }
    //컬렉션 뷰의 cell을 구성하는 함수
    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        //위에서 등록해준 커스텀 셀을 가져와서 리턴해줌
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId"for: indexPath)
        return cell
    }
    //커스텀 셀의 크기를 view에 꽉차게 해주었다.
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: view.frame.width, height: view.frame.height)
    }
    
}
 
cs

● UICollectionViewCell을 상속 받는 PageCell 작성

UICollectionViewCell을 상속받는 PageCell을 만들어 주었다. 여기서 반드시 overrid init() 메소드를 호출해서 부모 인스턴스를 초기화 해줘야 한다. 그래야 커스터마이징 할 수 있다. 그리고 viewController 함수에서 이미지 객체와 텍스트 객체를 가져와서 autolayout을 설정해준다. 이때 view가 기준이 아니라 cell이 기준이 된다. 그래서 지금 cell 안에서 autolayout을 작성하고 있기 때문에 기준인 cell. 이라고 굳이 적어줄 필요는 없다. 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
 
 
import UIKit
 
class PageCell: UICollectionViewCell {
    
    //클로저 기능으로 이미지객체 만들어주기(내부에서 속성정의)
    let myImageView: UIImageView = {
        let imageView = UIImageView(image: #imageLiteral(resourceName: "mypic1"))
        // 이속성은 autolayout을 이용할 수 있게한다.
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.contentMode = .scaleAspectFit
        return imageView
    }()
    ////클로저 기능으로 textView객체 만들어주기(내부에서 속성정의)
    let myTextView: UITextView = {
        let textView = UITextView()
        let attributedText = NSMutableAttributedString(string: "ios기초 레슨 프로그램", attributes: [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 18)])
        
        attributedText.append(NSAttributedString(string: "\n\n\n 할아버지 할머니도, 엄마도 아빠도 쉽게 배울 수 있는 아이폰 개발 강좌~!! 모두 열공 가즈아~~~~!", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 13), NSAttributedStringKey.foregroundColor: UIColor.gray]))
        
        textView.attributedText = attributedText
        
        //textView.text = "ios기초 레슨 프로그램"
        //textView.font = UIFont.boldSystemFont(ofSize: 18)
        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.textAlignment = .center
        textView.isEditable = false
        textView.isScrollEnabled = false
        return textView
    }()
 
    //부모 인스턴스를 초기화 해줘야 커스터마이징 해줄 수 있다.
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupLayout()
    }
    
    private func setupLayout() {
        
        let containerView = UIView()
        containerView.backgroundColor = .yellow
        //현재 cell에 콘테이너 뷰 추가
        addSubview(containerView)
        // 오토레이아웃 활성화
        //이제 제약조건이 view 기준이 아니기 때문에 view를 빼줘야 한다.
        containerView.translatesAutoresizingMaskIntoConstraints = false
        containerView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        containerView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        containerView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        //부모뷰에 이미지 뷰 넣어주기
        containerView.addSubview(myImageView)
        
        
        myImageView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).isActive = true
        myImageView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true
        myImageView.heightAnchor.constraint(equalTo: containerView.heightAnchor, multiplier: 0.5).isActive = true
        
        //부모뷰의 높이를 view의 절반으로 맞춰줬다.
        containerView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5).isActive = true
        
        //현재 cell에 textview 추가
        addSubview(myTextView)
        //myTextView의  topAnchor를 이미지뷰 아래에 맟추고 120 만큼 간격 띄어주기
        //이제 제약조건이 view 기준이 아니기 때문에 view를 빼줘야 한다.
        myTextView.topAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 120).isActive = true
        myTextView.leftAnchor.constraint(equalTo: leftAnchor, constant: 24).isActive = true
        myTextView.rightAnchor.constraint(equalTo: rightAnchor, constant: -24).isActive = true
        myTextView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
 
 
cs

● 마무리

UICollectionViewController에 대해서 알아 보았다. 이 view를 구현하려면 UICollectionViewController를 상속받고 또한 레이아웃을 설정해주기 위한 클래스 UICollectionViewDelegateFlowLayout을 상속받아야 한다. 그리고 UICollectionView를 구현해줄 메소드를 작성해줘야 한다. 반드시. 그리고 cell을 꾸며줄려면 UICollectionViewCell를 상속받는 클래스를 만들어주고 부모 인스턴스를 초기화 한다음에 사용해주면 되겠다.




+ Recent posts

티스토리 툴바