selector 선택자를 변수로 사용하기.


let sel = #selector(함수명)


다른 객체의 함수를 참조하는 경우


let sel = #selector(객체명.함수명) as ( 객체명 ) - > (파라미터 자료형) -> (함수반환형)

 


WRITTEN BY
arcjeen
ios 관련문의 slimforce@naver.com

,

action sheet 사용하기


action sheet = 아래에서 튀어나오는 메뉴. 보통 프로필이미지 지정 옵션을 줄때 사용.



        //action sheet title 지정

        let optionMenu = UIAlertController(title: nil, message: "Choose Option", preferredStyle: .actionSheet)

        

        //옵션 초기화

        let deleteAction = UIAlertAction(title: "Delete", style: .default, handler: {

            (alert: UIAlertAction!) -> Void in

        })

        let saveAction = UIAlertAction(title: "Save", style: .default, handler: {

            (alert: UIAlertAction!) -> Void in

        })

        

        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: {

            (alert: UIAlertAction!) -> Void in

      })

    

       //action sheet 옵션 추가.

        optionMenu.addAction(deleteAction)

        optionMenu.addAction(saveAction)

        optionMenu.addAction(cancelAction)

        

       //show

        self.present(optionMenu, animated: true, completion: nil)

        


delete, save, cancle등 원하는 옵션을 넣어서 초기화되면 된다. 원래는 actionsheet라는 클래스가 있었지만 


이제는 UIalertontroller로 대체!


WRITTEN BY
arcjeen
ios 관련문의 slimforce@naver.com

,

 

autolayout을 이용한 채팅창 메시지 UI구현


해외에서는 채팅창구현을 firebase + JSQMessageViewController 형태로하거나 tableview customcell을 이용하는것이 보통인데


이 글에서 설명할 것은 채팅 창이아니라 테이블뷰나 collectionView를 사용할때 cell안에 들어가는 autolayout을 이용해 동적인  message Box를 만드는 것을 목표로한다.


이 메시지박스를 이용해 tableview나 collectionView를 사용하면 간단하다.


1. UILabel을 이용해 메시지 박스 만들기


class UIMarginLabel: UILabel {

    

    @IBInspectable var topInset: CGFloat = 0.0

    @IBInspectable var leftInset: CGFloat = 0.0

    @IBInspectable var bottomInset: CGFloat = 0.0

    @IBInspectable var rightInset: CGFloat = 0.0

    

    var insets: UIEdgeInsets {

        get {

            return UIEdgeInsetsMake(topInset, leftInset, bottomInset, rightInset)

        }

        set {

            topInset = newValue.top

            leftInset = newValue.left

            bottomInset = newValue.bottom

            rightInset = newValue.right

        }

    }

    

    override func drawText(in rect: CGRect) {

        super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))

    }

    

    override func sizeThatFits(_ size: CGSize) -> CGSize {

        var adjSize = super.sizeThatFits(size)

        adjSize.width += leftInset + rightInset

        adjSize.height += topInset + bottomInset

        

        return adjSize

    }

    

    override var intrinsicContentSize: CGSize {

        var contentSize = super.intrinsicContentSize

        contentSize.width += leftInset + rightInset

        contentSize.height += topInset + bottomInset

        

        return contentSize

    }

    

}


먼저 UILabel 안쪽에 padding을 넣어주기위해 UIlabel Subclass를 하나 작성한다.

(여기서한가지의 팁을드리자면 채팅기능이아닌 단순한 padding이 필요한 라벨에 위의 클래스를 작성하여 서브클래싱할경우 UIbutton을 넣는것으로대체해도된다. UIbutton에는 이미 padding값이 있기때문!)


라벨을 하나만들어주고 customclass에 해당 클래스를 선언해준뒤 outlet을 연결해준다.


label의 line수는 0 으로 지정할것!


      //messageLabel 초기화

        missionDescripTionLabel.text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

        

        missionDescripTionLabel.insets = UIEdgeInsetsMake(10, 10, 10, 10);

        sitterCommentLabel.insets = UIEdgeInsetsMake(10, 10, 10, 10);

        

        view.layoutIfNeeded();



구현부에는 padding을 적용시켜줄 두개의 label을 초기화시켜주면 끝.




왜 이런 클래스를 작성해야하느냐? autolayout을 걸게되면 label은 자동으로 text영역에 딱 맞게 생성되게된다. 이것을 막기위해 width , height 에 eqaul constraints를 걸어 padding을 준것처럼 만들수도 있겠지만 그렇게된다면 컨텐츠크기에맞춰 label을 바꿧을때 해당 constraints의 constant 값을 조정해주어야하고 채팅창길이가 화면을 넘어갔을때도 디바이스크기별로 조건을 걸어줘야되기때문에 여간 귀찮은작업이아닐수 없다


그래서 autolayout을 이용하여 이과정을 줄이도록 한다.


이렇게선언하면 준비물은 끝났고 autolayout만 걸어주면된다.



2.Autolayout 걸기


autolayout 이놈은 어떤면에서는 편하지만 숙달되기전까지는 여간 사용하기힘든놈인것 같다.


정적인 페이지에서 정적인요소들만으로 구성한다고했을경우는 꽤 편하지만 동적으로 할당하여 uicomponent의 크기가 달라진다고 했을때 문제가좀 되게된다.


특히 scrollview에서...


나는기본적으로상단에 스크롤뷰가 필요했기때문에 하나 넣어주고 rootview에대해서


  left:0 right :0 top :0 bottom: 8 (왼쪽 이미지에대해서) height equal


을 걸어주었고


프로일 이미지의 센터와 label의 top을 이어주고 프로필이미지에는 width height equal 채팅 label에 대해서 leading rootView에대해서 8 trailing을 걸어주고 Y값 위치를 위해 왼쪽의 label의 bottom과 이미지뷰의 top에 8을 걸어주었다.


그림을봐도이해가되지않는다면 autolayout에관한 공부를 더하는것이좋을것같다..


여기서 이부분이 가장중요한데 label의 leading을 superview의 리딩에 연결해준다음


equal로 지정되어있는 relation을 Greater Than or Equal로 바꿔주고 constant는 본인이원하는 여백값으로 바꾸어주면 된다 본인은 채팅창과 왼쪽 끝의 최소거리를 10으로 지정했다.


greater Than or equal 속성은 두 attribute가 최소한 10의 거리는 유지해야한다는 말이다.


이렇게지정해준다면  끝!


실행해보면 텍스트가 길어졌을때 자동으로 라벨의 높이가조정되며 줄이바뀌는것을 확인할 수 있다. 





WRITTEN BY
arcjeen
ios 관련문의 slimforce@naver.com

,



개발하다보면 UIkit 클래스들 subclassing 해서 써야될때가있는데 이때 xib파일로 같이 작업을한다면 IBDesignable class를 통해


 xib 파일에서 간단한 프로퍼티 값들을 지정해줄 수 있다.


사용방법.


1. subclass 파일 만들고 class 선언. 본인은 테이블뷰로 진행




class 앞에 @IBDesignable 을 붙여주고


2. xib파일에 설정가능한 변수를 만들어준다




3. 만들어놓은 subclass의 super class를 xib파일에 추가하고 custom class에 본인이만든 subclass이름을 넣어준다


4. 그럼 inspector부분에 만들어놓은 property이름이 뜬다. 지정해주면 적용완료

5. 쉽다


WRITTEN BY
arcjeen
ios 관련문의 slimforce@naver.com

,


1. 카카오스토리 공유하기 - imagePost만 사용.


1. frameWork 추가


- kakaoOpenSdk Framework   파일 프로젝트로  import


- bridge-header에 #import <KakaoOpenSDK/KakaoOpenSDK.h> 추가


2. build setting 으로가서 other Linker flags 에 -all_load 추가


     - 64 비트 프로젝트나 아이폰 OS 응용 프로그램에서 클래스가 없고 카테고리만 있는 Static 라이브러리의 객체를  적재하려고 할 때 

 정상 작동을 하지 않는 버그를 대처하기위해 ‘-all_load‘를 추가해준다.

      ‘-all_load‘ 플래그는 모든 객체 파일을 링커로 부터 적재할 수 있도록 하는 플래그이다.



3. 카카오디벨로퍼페이지 설정에서 사용자관리 off -> on을 꼭해줄것.

4. 카카오디벨로퍼 페이지에나와있는 infolist랑 urlschema 기본설정을 해준다.
 
 카카오디벨로퍼 주소
 https://developers.kakao.com/docs/ios

5. appdelegate에서 urlopen 후 callback 함수 구현해준다.

    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {

     

            if(KOSession.isKakaoAccountLoginCallback(url)){

                return KOSession.handleOpen(url);

            }

 

        return true

}


    }

6. 카카오스토리 포스팅은 로그인세션이 연결되어있을경우 사용가능하기때문에 로그인세션을 열어줘야한다.




class UsingKakao{

    

    /**

     * 카카오톡 로그인.

     */

    

    class func kakaoLogin() -> Bool{

        

        var returnValue : Bool = true;

        

        //카카오톡 로그인창열기

        KOSession.shared().open(completionHandler:){ error in

            

            guard KOSession.shared().isOpen() else{

                print("로그인 취소")

                returnValue = false;

                return

            }

            

            if((error) != nil){

                returnValue = false

            }else{

                print("로그인 성공");

                returnValue = true;

            }

        }

        

        return returnValue;

        

    }

    

    

    /**

     * 카카오스토리 이용자인지 판별

     */

    

    class func checkKaKaoStoryUser(handler:@escaping (Bool) -> () ) {

        

        var returnValue : Bool = false;

        

        KOSessionTask.storyIsStoryUserTask(completionHandler:){ isStoryUser, error in

            

            if(!(error != nil)){

                

                if(isStoryUser){

                    print("story user")

                    returnValue = true;

                }else{

                    print("not storyuser");

                    returnValue = false;

                }

                

            }else{

                

            }

            

            handler(returnValue);

        }

    }

    

    

    /**

     *  카카오스토리 포스팅 with content image  permission으로 친구공개(KOStoryPostPermissionFriend) 또는 전체공개(KOStoryPostPermissionPublic) 또는 나만보기(KOStoryPostPermissionOnlyMe). default KOStoryPostPermissionPublic. optional.

     */

    

    class func postBabyJournalToKakaoStory(content : String, permission : KOStoryPostPermission, imageArr : NSArray){

        

        //카카오유저인지 체크

        UsingKakao.checkKaKaoStoryUser(){ result in

            

            //이용자가 아니라면 리턴.

            guard result else{

                return;

            }

            

            //카카오스토리 설치 확인 없으면 앱스토어로 이동.

            guard checkKakaoStoryInstalledUserDevice() else{

                return

            }

            

            //이미지 업로드 멀티파트리퀘스트

            KOSessionTask.storyMultiImagesUploadTask(withImages: imageArr as! [UIImage]){ imageUrls , error in

                

                guard error == nil else {

                    return

                }

                

                //포스트내용 post

                KOSessionTask.storyPostPhotoTask(withImageUrls: imageUrls, content: content, permission: permission, sharable: false, androidExecParam: nil,

                                                 iosExecParam: nil){ post, error in

                                                    

                                                    if (!(error != nil)) {

                                                        // 성공

                                                        print("postId : \(post?.id)");

                                                        

                                                        //카카오스토리로 이동

                                                        let kakaoStoryUrl = URL.init(string: "kakaostory://");

                                                        UIApplication.shared.open(kakaoStoryUrl!, options: [:], completionHandler: nil);

                                                        

                                                    } else {

                                                        // 실패

                                                        print("failed to post photo.");

                                                    }

                }

                

            }

            

        }

        

    }

    

    /**

     * 카카오스토리 설치유무 확인

     */

    

    class func checkKakaoStoryInstalledUserDevice() -> Bool{

        

        var returnValue = false;

        

        //카카오앱키

        let kakaoAppKey = "카카오앱키";

        

        // 실행 URL

        let kakaoStoryUrl = URL.init(string: "kakao\(kakaoAppKey)://kakaostory");

        

        //어플리케이션 실행확인

        if (!(UIApplication.shared.canOpenURL(kakaoStoryUrl!))) {


            //어플리케이션이 설치되어있지않다면 앱스토어로 이동

            let url  = URL(string: "https://itunes.apple.com/kr/app/%EC%B9%B4%EC%B9%B4%EC%98%A4%EC%8A%A4%ED%86%A0%EB%A6%AC/id486244601?mt=8")

            UIApplication.shared.open(url!, options: [:], completionHandler: nil);

            

        }else{

            returnValue = true;

        }

        

        return returnValue;

    }


}



소셜같은경우는 별도의 클래스를 만들어 관리를해주는것이 편리함.



7.사용


if (KOSession.shared().isOpen()){

            UsingKakao.postBabyJournalToKakaoStory(content: "test", permission: KOStoryPostPermission.onlyMe, imageArr: [journalimageView.image!]);

        }else{

            UsingKakao.kakaoLogin();

        }





WRITTEN BY
arcjeen
ios 관련문의 slimforce@naver.com

,


1.ROW


테이블뷰 안의 셀에 각각 다른 크기의 컨텐츠가 들어가서 높이를 다르게 지정해줘야 될 경우


tableview cell row height 에 UITableViewAutomaticDimension  속성을 주어 사용하면


테이블뷰가 자동으로 셀컨텐츠의 내용을 계산해서 높이를 맞춰준다.



 tableview.rowHeight = UITableViewAutomaticDimension;

 tableview.estimatedRowHeight = 130;



row height을 지정해준뒤 estimatedrowheight값을 지정해줘야되는데 이 프로퍼티는 


최초의 기본값정도라고 볼 수 있다.


저속성값대로 처음 지정되었다가 autolayout을 통해 뷰크기를 리사이징 해주는 것이다.


tableview cell 자체에 autolayout을 지정해주는 것이라고 보면된다.


위의 속성을 tableview가 선언된곳에서 지정해준뒤  아래와 같이 델리게이트 메서드를 


구현해주면된다.



    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {


return UITableViewAutomaticDimension


}


단순 텍스트를 담는 테이블뷰라면 위의 속성과 메서드만 구현해주어도 상관이없겟지만


customcell을 쓸때 주의할점은 cell의 contentView가 cell안의 ui요소 첫번째 요소의 top과


ui요소중 가장 아래에있는 요소의 bottom에 맞춰지게되는데 특정높이를 유지해야되는경우


에는 그 높이에맞는 view를 밑에 하나 깔아주고 equalheight constraint 조건을 걸어준뒤 


다른 요소들을 해당 view로 wrapping 해주면 된다.


contentview의 width의경우는 테이블뷰 크기에따라 자동으로 리사이징된다.


2.section


section header 또한 위의 같은 방식으로 dynamic하게 높이를 지정해줄 수 있지만


왜인지 custom section header를 사용할경우 estimateheight 메서드를 사용할 수 


없게 되어 있다. 이는 애플 가이드 문서에보면 


viewforheader in section 델리게이트 메서드는  heightforheader in section 메서드와 같이 선언되었을때 사용할 수 있다.


고나와있다. 그러므로 커스텀 헤더를 쓸 경우에는 전역변수로 헤더 높이를 잡아주고 


그 값을 리턴해주는수 밖에 없는 것 같다.. 


 func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

      

       let sectionHeader = UIView.init(frame: CGRect.init(x: 0, y: 0, width: self.frame.width, height: 40))

        

        return sectionHeader;

    }

    

    

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {

        return 40;

    }

    

    func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {

        

    }





custom section header를 사용할 경우 viewforheaderinsection이라는 메서드를통


해 지정해주면되는데 만약  tableview 선언부에서 section height에 


uitableviewautodemention 속성을 걸어주고 estimateheightforHeaderInSection 


메서드를 구현하면  viewforheaderinsection 메서드가 호출되지 않는다.



*TIP


만약 드롭 다운 형식의 테이블뷰를 사용하고 있을때 테이블뷰 크기를 동적으로 계속 


할당해 주어야 한다면

var heightOfTableView: CGFloat = 0.0

            let cells = self.visibleCells

            for cell in cells {

                heightOfTableView += cell.frame.height

            }


위의방법을통해 보이는 셀의 높이를 계산해주어 동적으로 바꾸어주는게좋다





WRITTEN BY
arcjeen
ios 관련문의 slimforce@naver.com

,

UIKeyboard Height 구하기.


NotificationCenter.default.addObserver(self, selector:#selector(keyboardWillShowing(notification:)), 

name: Notification.Name.UIKeyboardWillShow, object: nil);


viewdidload 나 원하는곳에 UIkeyboardwillshow 일때 수행할 메서드를 노티피케이션센터에 등록해준다. 뭐 UIkeyboardwillHide도 있고 상황에따라 변경해서쓰시면된다.


func keyboardWillShowing(notification:NSNotification){

    

        let userInfo:NSDictionary = notification.userInfo! as NSDictionary;

    

        let keyboardFrame:NSValue = userInfo.value(forKey: UIKeyboardFrameEndUserInfoKey) as! NSValue

        let keyboardRectangle = keyboardFrame.cgRectValue;

        let keyboardHeight = keyboardRectangle.size.height;

        print(keyboardHeight);

}


키보드정보는 noti 객체에 담겨서온다. 여기서 프레임값을뽑아내면 끝!


WRITTEN BY
arcjeen
ios 관련문의 slimforce@naver.com

,



1. UIDatePicker 

- 시간 및 날짜를 선택할 수 있게해주는 UI클래스인데 클래스 자체에 델리게이트 메서드가 없어져 valuechange라는 이벤트 상태에 메서드를 붙여줘야 활용이가능하다. 뭐 실시간 반응이 필요 없으면 그냥 datepicker 의 date만 받아와서 사용해도 무방하다.



2. UIDatePicker를 이용한 시작 종료 날짜 구현


 @IBOutlet weak var dolbomStartTimeView: UIView!

    @IBOutlet weak var dolbomStartTimeLabel: UILabel!

    @IBOutlet weak var dolbomStartDateLabel: UILabel!

    @IBOutlet weak var dolbomEndTimeView: UIView!

    @IBOutlet weak var dolbomEndDateLabel: UILabel!

    @IBOutlet weak var dolbomEndTimeLabel: UILabel!

    @IBOutlet weak var datePickerView: UIDatePicker!


    var dolbomStartTimeViewIsSelected : Bool = true;



일단 아웃렛먼저 잡아준다. dolbomStartTimeView 는 시작시간에 해당하는 뷰 dolbomStartTimeLabel은 시간 DateLabel은 날짜를 표시해주는 라벨인데 뭐 라벨하나로도 linebreak wordwrap 속성 걸어주고 해도되겠지만 그냥 두개나누는게 글자크기따로바꾸기편해서 두개로 뒀다.


아그리고 시작시간과 종료시간중 어떤것이 선택되어있는 상태인지를 판별하는 bool변수 하나 선언해준다. 맨처음 view가 load되었을때 시작시간이 선택된 상태이므로 값은 true로 준다.


 self.datePickerView.minuteInterval = 10;

        datePickerView.addTarget(self, action: #selector(dolbomDatePickerIsChanged(picker:)), for: .valueChanged);

        


minuteInterval은 분선택 picker에서 표시되는 분단위를 설정하는건데 1분단위로 나오게하고싶다면 그냥 따로 설정을하지않으면된다.

그다음 datepicker 값이 변경될때 실행되는 메서드를 추가해준다. 내가 사용한 메서드는 solbomDatePickerIschanged.




 func dolbomDatePickerIsChanged(picker:UIDatePicker) {

        

        if(dolbomStartTimeViewIsSelected){

            let dateFormatter : DateFormatter = DateFormatter();

            

            dateFormatter.dateStyle = .full;

            

            let timeFormatter : DateFormatter = DateFormatter();

            

            timeFormatter.timeStyle = .short;

            

            dolbomStartDateLabel.text = dateFormatter.string(from: picker.date);

            dolbomStartTimeLabel.text = timeFormatter.string(from: picker.date);

            

            

        }else{

            

            let dateFormatter : DateFormatter = DateFormatter();

            

            dateFormatter.dateStyle = .full;

            

            let timeFormatter : DateFormatter = DateFormatter();

            

            timeFormatter.timeStyle = .short;

            

            dolbomEndDateLabel.text = dateFormatter.string(from: picker.date);

            dolbomEndTimeLabel.text = timeFormatter.string(from: picker.date);

            

        }

    }


DateFormatter 라는것은 date값을 입력받아서 지정된 포맷의 스트링으로 반환해준다던지 지정된 포맷의 스트링을 받아 date형으로 다시돌려주는 역할을 해준다. 이거하나면 날짜 정복가능하다.


뭐 별로의 형식으로 직접 스트링으로 지정해주지않아도 style 속성의 여러옵션을 이용해서 지정가능하다.


두개의 라벨을 쓸거기때문에 포메터를 두개 선언해서 하나는 날짜포맷 하나는 시간포맷으로 설정해서 스트링으로반환되는 값을 라벨에다가 붙여줬다.

한라벨에 쓰고싶으면 그냥 formatter 하나에 datestyle, timestyle 두개다 추가해주면된다.


    func timeEndViewSelected(gesture : UITapGestureRecognizer){

        

        dolbomStartTimeView.backgroundColor = UIColor.white;

        dolbomEndTimeView.backgroundColor = UIColor.blue;

        

        if let _ = dolbomEndTimeLabel.text?.isEmpty {

            

            let dateFormatter = DateFormatter();

            dateFormatter.dateFormat = "yyyy M dd eee a h:mm"

            

            dateFormatter.amSymbol = "오전";

            dateFormatter.pmSymbol = "오후";

            

            let dateString = "\((dolbomEndDateLabel.text)!) \((dolbomEndTimeLabel.text)!)";

            

            let date = dateFormatter.date(from: dateString);

            

            datePickerView.date = date!;

            

        }else{

            

        }

        

        dolbomStartTimeViewIsSelected = false;

        dolbomStartTimeView.isUserInteractionEnabled = true;

        dolbomEndTimeView.isUserInteractionEnabled = false;

        

    }


이제 뷰가 선택되었을때 데이트피커가 자동으로 선택된뷰의 날짜와 시간으로 변경되게하는 부분인데

위의 데이트 받아오는부분을 반대로해주면 된다. 데이트 포맷을 string으로 받아와준다음 다시 date값으로바꿔주고 이것을 date picker의 date property에 집어넣어주면 자동으로 피커의 값이 바뀌게된다.


eee a h:mm 에서 a는 am,pm을 나타낸다.


그냥 혹시나있을 사용자버그 처리할려고 선택이되어있는뷰는 interaction을 불가능하게만들어준다.




 func setFirstPickerDateTimeLable(){

       

        var nowDateTime = Date();

        

        let tmpDateFormatter = DateFormatter();

        tmpDateFormatter.dateFormat = "yyyy.M.dd.e.HH.mm";

        

        let dateString : NSString = tmpDateFormatter.string(from: nowDateTime) as NSString;

        var dateArray : NSMutableArray = NSMutableArray.init();

        dateArray = dateString.components(separatedBy: ".") as! NSMutableArray;

        

        let tmpMin = Int(dateArray.object(at: 5) as! String)! % 10;

        nowDateTime.addTimeInterval(TimeInterval((10-tmpMin)*60));

        

        let dateFormatter = DateFormatter();

        dateFormatter.dateFormat = "yyyy M dd eee요일";

        

        let timeFormatter = DateFormatter();

        timeFormatter.dateFormat = "a h:mm";

        timeFormatter.amSymbol = "오전";

        timeFormatter.pmSymbol = "오후";

        

        dolbomStartDateLabel.text = dateFormatter.string(from: nowDateTime);

        dolbomEndDateLabel.text = dateFormatter.string(from: nowDateTime);

        

        dolbomEndTimeLabel.text = timeFormatter.string(from: nowDateTime);

        dolbomStartTimeLabel.text = timeFormatter.string(from: nowDateTime);

        

        

        let pickerDateFormatter = DateFormatter();

        pickerDateFormatter.dateFormat = "yyyy M dd eee a h:mm"

        

        pickerDateFormatter.amSymbol = "오전";

        pickerDateFormatter.pmSymbol = "오후";

        

        let pickerDateString = "\((dolbomStartDateLabel.text)!) \((dolbomStartTimeLabel.text)!)";

        

        let pickerDate = pickerDateFormatter.date(from: pickerDateString);

        

        datePickerView.date = pickerDate!;


    }




이제 뷰가 로드됬을때 라벨의 시간을 초기화시켜줘야하는데 date 객체를 생성하면 기본적으로 현재시간을 받아온다. 현재 시간과 날짜를 . 으로나눈뒤 배열에 각각 저장하였다. 나는 시간을 10분단위로 설정하기때문에 현재 분 의 일자리단위를 버리고 10분이추가된상태의 시간을얻고싶어서 따로처리를해주었다.

그냥 현재시간그대로얻고싶다면 중간과정생략해도상관없다.


일단 10분단위로 쪼개는 과정은 13분이면20분 25분이면 30분 이런식의 표현을원하기때문에 일자리단위를 구해줘야하는데 그냥 % 10 해주면된다.


그리고 date클래스에는 addTimeInterval이라는 아주 편리한 메서드가있다. 받아온시간에서 초단위로 몇초뒤의 시분날짜를 자동으로 계산해준다. 여기서 내가필요한것은 일의자리를뺀 이후의 10분값이기때문에 (10-일의자리) * 60값을 더해준다.(일의자리는 분단위고 addinterval하는것은 초단위이기때문)


혹시나하는마음에 timeinterval에 대한 설명을 해놨는데 if문으로 시간처리하는 바보같은짓은 하지않길바란다.. 

그뒤에 위에서와같이 picker또한 초기화시켜주면끝!



3.exception

개발언어 english로두면 아마 optional 오류가날텐데 date형식때문에그렇다. 뭐 localization해주면상관없는데어차피한국에서만쓸거니 pass

한국어설정하는방법은혹시나해서... 시뮬레이터 설정 -> 일반-> 언어및지역 -> 한국어선택해주시면된다.






WRITTEN BY
arcjeen
ios 관련문의 slimforce@naver.com

,

다음 지도 api


아직도 호환이잘안된다.

swift나온지가언젠데 후우 obj-c에서 프레임워크를 추가하면 bitcode 오류가 안나는데 swift에서는 왜나는지 모르겠다.


아 bitcode라는건 앱스토어에서받을때 사용자 기기에맞춰서 앱을 컴파일해서 설치해주는거라고 보면된다.


내경우 swift 프로젝트에서 다음지도 api를 사용할경우 해당 프레임워크가 bitcode를 포함하고있지않다고 뜬다 나만그런가.. 어디를 검색해도 해당오류를 갖고있다는 글은 못봣다 한국기업 api라서그런가...

물론그냥 프로젝트 세팅에서 bitcode enable 속성을 NO로 주면 해결가능하다. bitcode사용은 optional이라고하더라 그래도 사용하라고 해놓은걸 프레임워크하나때문에 사용하지않을수는 없지 않은가  그래서 고민끝에 프로젝트하나를 새로만들고 거기에 모듈로 인식을하게한다음 사용을해보기로했다.


잘된다.


뭐 이게맞는방법인지는 모르겠지만 어떻게하는건지는 알려드리겠다.


먼저 귀찮아 그냥 bitcode 안쓰지뭐 하시는분들은


빌드셋팅 들어가셔서 bitcode검색하신다음 저거 NO로 설정하시면된다.


그래도나는 bitcode사용하겠다 하시는분들은


1. 프레임워크프로젝트를 하나 생성한다.



뭐 이런고민하시는분들이 프레임워크프로젝트 생성방법조차 모른다고 생각하진않지만 혹시모르니 스샷찍어드림


2. 다음맵 프레임워크 프로젝트에 추가해주시고 empty파일로 만드는데 파일이름은 module.modulemap 으로 지정해주시면된다.



저기 module.modulemap에


module
 DaumMap {

    header "DaumMap.framework/Headers/MTMapView.h"

    header "DaumMap.framework/Headers/MTMapPolyline.h"

    header "DaumMap.framework/Headers/MTMapReverseGeoCoder.h"

    header "DaumMap.framework/Headers/MTMapPOIItem.h"

    header "DaumMap.framework/Headers/MTMapLocationMarkerItem.h"

    header "DaumMap.framework/Headers/MTMapGeometry.h"

    header "DaumMap.framework/Headers/MTMapCircle.h"

    header "DaumMap.framework/Headers/MTMapCameraUpdate.h"


    export *

}


이거 넣어주시면된다



3. 이제 모듈맵 파일 경로지정하는 config파일 하나만들어주시면끝난다 쉽다.



저기 configuration settingFile 하나 만드시고 이름은 Config.xcconfig 그냥아무렇게나해도될거같은데 안해봐서모르겠다.


저기안에

SWIFT_INCLUDE_PATHS = $(SRCROOT)/

MODULEMAP_PRIVATE_FILE = $(SRCROOT)/module.modulemap


이거넣어주신다음 swift 브릿지헤더에 헤더파일 import 추가하시면 끝.

본격적으로  프레임워크사용하지는않았지만 클래스 자동완성이뜨는거보면 사용가능할거같다. 컴파일도 잘된다.






2017.7.10

추가


간단한 마커정도 띄우는용도면 다음지도 사용하시는것도 괜찮은데 경로그리기나 경로까지 도달하는 시간 등의 기능을 가진 앱일 경우 애플맵킷이나 

구글 맵킷을 사용하는 것을 추천드립니다.


애플맵킷의경우 도달경로를 구하는데 도보, 자동차 , 자전거의 예상시간 경로등을 리퀘스트 제한없이 사용할수 있다는 장점이 있고


구글맵킷의 경우 도달경로를 구하는데 좀더 상세한 정보를 얻을 수 있지만 리퀘스트 제한이 있다는 단점이 있습니다.


애플 맵킷이 구글지도에 비해 우리나라 장소정보에대한 정보가 부족하다고하는 의견들이 있는데 mapkit 도큐먼트를 확인해보면 구글 지도 데이터소스를 사용한다고 되어있습니다. 정확한비교는해보지않아서모르겟지만 간단한 경로그리기나 마커등을 추가하는것이라면 사용하기편리한 애플 mapkit을 추천드립니다.













WRITTEN BY
arcjeen
ios 관련문의 slimforce@naver.com

,

네이버 로그인 연동


1. 대충 네이버 개발자 센터에가서 ios sdk를 받아준다

2. sdk 폴더안에있는 소스파일 전부 프로젝트로 붙인다.

3. 전부다 안붙이면 밑에 있는 에러난다.. .




저기 libNaverLogin 파일 프로젝트에 안붙였다가 링크에러 계속났었다.


4. 브릿지헤더만든다.

sdk 헤더파일들 임포트해주면된다.


5 나머지 info.list  설정같은거는 네이버 홈페이지에나와있는대로 해주면된다.


6. ios 9 부터는 querySchema 설정해줘된다 안하면 밑에처럼 에러가뜬다.

 



요기다 naversearchapp, naversearchthirdlogin 추가해주면 끝!


추가


카카오나 페북에비해서 sdk가 좀 지랄맞다 


1. appdelegate에서 상수세팅 해준다.



2.appdelegate에서 url 콜백되었을때 처리 하는 부분 넣어준다.


3. 구현하고자하는 뷰에서 NaverThirdPartyLoginConnectionDelegate 선언해주고 저프로토콜 필요한 메서드 전부 구현해준다(사진에 oauth로시작하는 메서드 다 required 찾기 귀찮게 왜 저렇게해놧는지모르겠다.)


4. 로그인 액션할 부분에 loginconnection 개체 만들어준후 delegate 해준다 여기서 delegate 안걸면 네이버앱으로는 연결이되는데 네이버앱 없을시 인앱브라우저가 안뜬다.


5. 마지막으로 내경우에는 네비게이션컨트롤러가 루트뷰인데 인앱브라우저에서 닫기 버튼 눌렀을경우 웹뷰가 안꺼졌다. present하는 부분이 어떻게 구현되있는지는 모르겠지만 원래 sdk NLoginThirdPartyOAuth20InAppBrowserViewController.m에는 dissmissview로 되어있다. 이거 popviewcon으로바꿔준다 그럼꺼진다.


 


2017.12.11


로그인연동에관한 문의메일을 받았었습니다.

nts 정책에따른 white domain을 추가해주셔야 네이버앱이 설치되어있지않은경우 webview가 호출되게됩니다.


<key>NSAppTransportSecurity</key>

<dict>

<key>NSAllowsArbitraryLoads</key>

<true/>

<key>NSExceptionAllowsInsecureHTTPLoads</key>

<dict>

<key>NSIncludesSubdomains</key>

<true/>

</dict>

<key>NSExceptionDomains</key>

<dic>

<key>naver.com</key>

<dict>

<key>NSExceptionAllowsInsecureHTTPLoads</key>

<true/>

<key>NSExceptionRequiresForwardSecrecy</key>

<false/>

<key>NSIncludesSubdomains</key>

<true/>

</dict>

<key>naver.net</key>

<dict>

<key>NSExceptionAllowsInsecureHTTPLoads</key>

<true/>

<key>NSExceptionRequiresForwardSecrecy</key>

<false/>

<key>NSIncludesSubdomains</key>

<true/>

</dict>

</dict>

</dict>



plist에서추가해주세요!






WRITTEN BY
arcjeen
ios 관련문의 slimforce@naver.com

,