제목그대로 네비게이션바에다가 커스텀뷰를 붙이는 방법에는 두가지가 있다.


1. 네비게이션바에서 제공하는 titleView 메소드를 이용하는 방법


UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 120.f, 44.f)];

UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(10.f, 10.f, 100.f, 24.f)];

lbl.backgroundColor = [UIColor whiteColor];

lbl.text = @"custom label~";

lbl.font = [UIFont systemFontOfSize:12.f];

customView.backgroundColor = [UIColor blueColor];

[customView addSubview:lbl];


[self.navigationItem setTitleView:customView];


2. 네비게이션바에다가 직접  addSubview로 붙이는 방법


self.customButton = [UIButton buttonWithType:UIButtonTypeSystem];

    [_customButton setTitle:@"Click!" forState:UIControlStateNormal];

    _customButton.frame = CGRectMake(10.f, 10.f, 50.f, 24.f);

    [_customButton addTarget:self action:@selector(clicked:) forControlEvents:UIControlEventTouchUpInside];

    [self.navigationController.navigationBar addSubview:_customButton];

    

    self.iconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(60.f, 0.f, 40.f, 40.f)];

    _iconImageView.image = [UIImage imageNamed:@"hanjimin"];

    [self.navigationController.navigationBar addSubview:_iconImageView];

    

    

    self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(_iconImageView.frame.origin.x + 40.f,

                                                                10.f, 100.f, 24.f)];

    _titleLabel.text = @"jimin jjang!!";

    [_titleLabel sizeToFit];

    _titleLabel.textColor = [UIColor whiteColor];

    _titleLabel.backgroundColor = [UIColor orangeColor];

    [self.navigationController.navigationBar addSubview:_titleLabel];



위의 두가지 방법 다 유효하다. 다만 붙이는 커스텀된 뷰가 레이아웃이 가변적일때(유저조작에 따라 변경되야 할 경우)는 고민을 해 봐야 한다.


이 문제로 3시간 삽질을 했는데 삽질결과 알아낸 사실은..


- 1.번의 방법으로는 레이아웃 변경자체가 안되고 그냥 가운데로 고정이 되버림.


- 2.번의 방법으로 레이아웃은 조정 가능하지만 만약 커스텀뷰 안에 하위 뷰가 존재할 경우 하위 뷰는 레이아웃 변경이 안된다.(오토레이아웃, 오토리사이즈, 코드상 변경 모두 불가)

 네비게이션에 직접 붙인 뷰(하위1단계레벨)만이 레이아웃 조정가능했다.


버튼이벤트에 레이아웃을 움직여보면.. 잘움직인다.


- (void)clicked:(id)sender {

    

    //update layout!

    _iconImageView.frame = CGRectMake(arc4random()%200 + 60.f,

                                      _iconImageView.frame.origin.y,

                                      _iconImageView.frame.size.width,

                                      _iconImageView.frame.size.height);

    

    _titleLabel.frame = CGRectMake(_iconImageView.frame.origin.x + 40.f,

                                   _titleLabel.frame.origin.y,

                                  _titleLabel.frame.size.width,

                                  _titleLabel.frame.size.height);

    

}





결론은..


네비게이션뷰에다가 복수개의 뷰를 붙일경우엔 편의상 컨테이너뷰를 만들고 뷰 하나만 붙이면 된다고 생각하지만 그렇게 되면 레이아웃 조정이 불가능해지므로 따라서 각각 하나하나 붙여야 한다.


예를들어.. 내가 삽질을 하게 된 이유이지만, 네비게이션뷰에 드롭다운 메뉴를 만들고 드롭다운 메뉴에서 선택된 항목으로 인하여 라벨크기가 가변적으로 변해야 한다면 라벨, 버튼, 이미지 각각 따로 붙여야 한다.








Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

단위를 포함한 텍스트를 표시할 경우 대게 숫자표시와 단위의 폰트색상이나 크기가 다르게 디자인 되는 경우가 많다.


이제까지는 라벨을 두개로 나누어(숫자, 단위)서 처리했는데 이런방식으로 할때 문제점은 숫자표시에서 자리수가 바뀔경우(동적으로) 단위 레이아웃을 조정해 줘야 하는 귀찮음이 생긴다.


그런데 어트리뷰트스트링을 사용하면 하나의 라벨에서 두가지 디자인을 대응할 수 있기에 편리하다. 예전부터 이 클래스는 알고 있었으나 여기까지 생각이 미치지 못해 응용을 하지 못했다능...



이미 만들어버린 부분은 어쩔수 없어도 다음부터는 이런 스타일을 카테고리화 해서 사용하면 유용할 것 같은 생각이 든다.





NSDictionary *valueAttribute = @{ NSForegroundColorAttributeName:[UIColor blackColor],

                                       NSFontAttributeName:[UIFont systemFontOfSize:16.0f]};


NSAttributedString *valueString = [[NSAttributedString alloc] initWithString:@"1234"

                                                              attributes: valueAttribute];

    


NSDictionary *unitAttribute = @{ NSForegroundColorAttributeName:[UIColor lightGrayColor],

                                                NSFontAttributeName:[UIFont boldSystemFontOfSize:12.0f] };

NSAttributedString *unitString = [[NSAttributedString alloc] initWithString:@"km"

                                                              attributes:unitAttribute];

    

    

NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] init];

[mutableAttributedString appendAttributedString:string1];

[mutableAttributedString appendAttributedString:string2];


//라벨에 텍스트를 설정

_label.attributedText = mutableAttributedString;


Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

가끔씩 디자인 편의상 전면에 알파값을 준 투명한 뷰로 덮을 경우가 있다.

이러한 뷰에서 터치이벤트를 받지 않고 통과시켜 뒤에 있는 뷰가 동작할 수 있도로 하기 위해서는 아래와 같이 메소드를 오버라이드 해주면 된다.





- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {

    

    //자신의 뷰에서 터치이번트를 통과시키고 싶을경우에 요렇게..

    UIView *touchedView = [super hitTest:point withEvent:event];

    if([self isEqual:touchedView] == YES){

        return nil;

    }

    

    return touchedView;

}



메소드명이 테스트라.. 뭔가 했는데 이런용도가 있었다능...

Posted by 악당잰 트랙백 0 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply 김기봉 2015.01.26 14:09

    view.isUserInteractionEnabled = NO;
    이렇게 해당 뷰에 옵션을 줘도 통과가 된다고 알고 있어요.

개발을 하다보면 더미 데이타(테스트 데이타)가 필요할 때가 있다.  뭐 루프 돌려서 인덱스 붙이는 식으로 데이터를 만들어서 쓰기는 하는데..




로직테스트 이외에 실데이터로 돌렸을 때에 아무래도 생각치 못했던 문제들이 발견 때문에 가급적 실제 데이터 같은걸 만들 고 싶었는데 구글링을 해보니 꽤 많이 발견되었다.

게다가 테스트용 이미지도 생성해주는 사이트도 덤으로 발견!



나중에 유용할지 모르니 메모식으로 남겨둔다.



테스트 이미지 생성

프로필사진이미지 생성 (영어)

http://uifaces.com/

테스트 이미지생성

 http://placehold.jp/

img태그URL로 지정가능(http://placehold.jp/{넓이}x{높이}.png)

http://placeimg.com/

카테고리별로 지정가능


테스트 문자열 생성 

일본어 텍스트를 생성(일본어)

Source URL: http://lipsum.sugutsukaeru.jp/

HTML태그 붙어있는 텍스트 출력 (영어)

Source URL: http://www.blindtextgenerator.com/snippets?snipps=ANY-snippets-lorem


일본이름 생성

 http://g-chan.dip.jp/square/archives/2009/09/trpg_2.html

영어이름 생성

Source URL: http://random-name-generator.info/



참고사이트


http://qiita.com/yoshimana/items/ebd69b9961429ce1eacf

http://www.ideaxidea.com/archives/2011/04/placeholdersmatome.html



Posted by 악당잰 트랙백 0 : 댓글 1

댓글을 달아 주세요

  1. addr | edit/del | reply 김기봉 2015.01.26 14:12

    역시 세상에는 같은 문제로 고민을 하는 사람이 분명 있는듯..
    좋네요.

저번 1편에서 설명한 환경설정이 제대로 되었다면 이제 코딩만 하면 된다. 나 또한 회사 지인의 부탁으로 크롤러를 만들어 보았다. 

프로젝트를 생성하게 되면 몇개의 폴더와 파일들이 생기는데 적절히 설정해주고 로직이 필요한 부분에 코딩을 해주면 된다.


우선 프로젝트를 생성하기 위해서는 아래의 커맨드를 입력한다.(저번에 설명했었나?)

scrapy startproject townpage





프로젝트명을 townpage로 지정하였기 때문에 townpage라는 폴더가 생성되고 그안에 아래와 같은 파일들이 생성되어 있을것이다.

townpage

 |

 ----  scrapy.cfg   ---> 프로젝트정보를 가지고 있음

 ---- /townpage   프로젝트 폴더

            |

            ----- items.py      크롤링할 데이타 모델

            ----- pipelines.py  크롤링 결과를 파일로 저장할때 필요한 룰(규칙) 정의

            ----- setting.py item과piplines과의 설정 및 각종? 설정에 필요

            ----- /spiders   크롤링할 거미들을 모아놓을 폴더

                       |

                       ----- townpage.py (크롤링을 위한 거미. 처음에 생성되지 않으므로 만들어야함.)


이것저것 생성이 되었지만 실제로 코딩이 필요한것은 /spiders 폴더 밑에 거미들이고 나머지는 설정정도의 레벨에서 충분하다.




그럼 하나씩 소스를 보자.

#items.py


# -*- coding: utf-8 -*-


# Define here the models for your scraped items

#

# See documentation in:

# http://doc.scrapy.org/en/latest/topics/items.html


import scrapy

from scrapy.item import Item, Field


class TownpageItem(scrapy.Item):

shopname = Field()

comment = Field()

address1 = Field()

address2 = Field()

tel = Field()

map_url = Field()

request_num = Field()

column_num = Field()


TownpageItem이라는 클래스를 만들고 정보를 수집하고 싶은 항목들을 아래와 같이 정의해준다. 주의할 것은 소스를 utf-8로 지정해줄것과 import를 잊지않고 추가해주는것 정도.





#Pipelines.py


# -*- coding: utf-8 -*-


# Define your item pipelines here

#

# Don't forget to add your pipeline to the ITEM_PIPELINES setting

# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html


from scrapy.exceptions import DropItem

import codecs

import json


class JsonWriterPipeline(object):


    def __init__(self):     

self.file = codecs.open("items.json", "wb", encoding="utf-8")


    def process_item(self, item, spider):

line = json.dumps(dict(item), ensure_ascii=False) + "\n"

self.file.write(line)

return item


출력될 데이타를 어떻게 출력할 것인지를 정의 하는 곳이다. 내 경우는 문자가 깨지는 현상이 있어서 utf-8로 출력되도록 소스를 좀 손을 보았다. 각각의 환경에 맞추어 정의 해주어야 하므로 스택플로우에 도움을 받도록 하자.






#settings.py


# -*- coding: utf-8 -*-


# Scrapy settings for townpage project

#

# For simplicity, this file contains only the most important settings by

# default. All the other settings are documented here:

#

#     http://doc.scrapy.org/en/latest/topics/settings.html

#


BOT_NAME = 'townpage'


SPIDER_MODULES = ['townpage.spiders']

NEWSPIDER_MODULE = 'townpage.spiders'

DEFAULT_ITEM_CLASS = 'townpage.items.TownpageItem'


ITEM_PIPELINES = {'townpage.pipelines.JsonWriterPipeline': 1}

# Crawl responsibly by identifying yourself (and your website) on the user-agent

#USER_AGENT = 'townpage (+http://www.yourdomain.com)'


여기서는 프로젝트에 사용할 아이템(모델)을 정의해주고 파이프라인을 지정해 준다. 중요한건 TownpageItem을 지정해 주는거다.




그리고 여기서 제일 중요한 부분. 크롤링을 해주는 거미이다. 


#townpage.py


# -*- coding: utf-8 -*-


from scrapy.spider import Spider

from scrapy.selector import Selector

from scrapy.http import Request


from ..items import TownpageItem


print "============================="

print "start townpage crawling..."

print "============================="


max_url_count = 10

current_url_count = 1


#크롤링할 URL

domain = u"http://itp.ne.jp"

url_path = u"/tokyo/genre_dir/gourmet/pg/"

param = u"/?sr=1&nad=1&num=50"

url = domain + url_path + u"1" + param

# url = domain + u"/tokyo/genre_dir/gourmet/?sr=1&nad=1&evdc=1&num=50"


class TownpageSpider(Spider):

    name = "townpage"

    allowed_domains = ["itp.ne.jp"]

    start_urls = [url]


    print "start :" + str(start_urls)

    

    def parse(self, response):


        global current_url_count, max_url_count


        #shoplist를 취득

        print "parsing shop pagelink..." + str(current_url_count)


        hxs = Selector(response)


        shops =[]

        #shop정보취득

        shops = hxs.xpath('//div[@class="normalResultsBox"]/article[1]/section[1]')

        print "shops count: " + str(len(shops))

        

        items = []

        row_num = 1

        for shop in shops:

            item = TownpageItem()

            item['shopname'] = shop.xpath('h4[1]/a[1]/text()').extract()

            item['comment'] = shop.xpath('p[1]/text()').extract()

            item['address1'] = shop.xpath('p[2]/text()').extract()

            item['tel'] = shop.xpath('p[3]/b[1]/text()').extract()

            item['map_url'] = = shop.xpath('p[2]/a/@href').extract()

            item['page_num'] = str(current_url_count)

            item['row_num'] = str(row_num)

            row_num = row_num + 1


            yield item

            # items.append(item)


            # print "[shopname] " + str(item['shopname'])

            # print "[comment] "+ str(item['comment'])

        


        if(current_url_count <= max_url_count):

            current_url_count = current_url_count + 1

            next_page = [domain + url_path + str(current_url_count) + param]

            print "------->request url :" + next_page[0]

            yield Request(next_page[0],self.parse)

            

            # yield Request(next_page[0],self.parse)



        # return items



        


로직을 잠깐 설명하자면 시작페이지가 리스트(일람)페이지인데 가게정보를 얻어오고 다음페이지로 URL을 변경한다음 재귀 호출하여 처리하는 방식이다. 간단하니 소스를 보면 금방 이해될 것이다.






자! 이제 크롤링을 해보자.

#Json으로 출력하기

scrapy crawl townpage -o items.json


지인으로부터 CSV파일로 해줬으면 하는 부탁이 있어 검색을 해보니 아니나다를까 간단한 방법이 있었다.

#CSV출력하기

scrapy crawl townpage -o items.csv -t csv


제대로만 했다면 아래와 같은 실행결과 화면을 만날것이다.


파이썬 초보인데다가 웹기술도 그다지 지식이 없어서 삽질정도의 설명이 되고 말았지만 이글이 혹시라도 나와같은 삽질을 줄이는데 도움이 됬으면 한다. 끝~




Posted by 악당잰 트랙백 0 : 댓글 6

댓글을 달아 주세요

  1. addr | edit/del | reply 2015.01.19 12:35

    비밀댓글입니다

    • addr | edit/del BlogIcon 악당잰 2015.01.20 07:49 신고

      크롬이나 파폭으로 HTML소스 보시면서 웹페이지를 분석하면 되는데 정확히 어떤부분이 잘 안되시나요?

  2. addr | edit/del | reply 도와주세요 2015.01.20 22:07

    와 답변 남겨주셔서 정말 감사합니다...
    시작단계인데요... 400에러가 나서 시작도 못하고있어요...아무리 찾아봐도.. 원인을 모르겠어요..
    아래와 같이 코드 짜서.. 일단 돌려보려고하는데요.. 아래와 같이 400에러가나요 ㅠ
    혹시 원인 알려주실수 있을까요? 정말 감사합니다. 복받으실꺼에요..!

    2015-01-20 22:02:52+0900 [scrapy] DEBUG: Telnet console listening on 127.0.0.1:6023
    2015-01-20 22:02:52+0900 [scrapy] DEBUG: Web service listening on 127.0.0.1:6080
    2015-01-20 22:02:52+0900 [heny] DEBUG: Retrying <GET http://m.map.naver.com/siteview.nhn?code=11687099> (failed 1 times): 400 Bad Request
    2015-01-20 22:02:52+0900 [heny] DEBUG: Retrying <GET http://m.map.naver.com/siteview.nhn?code=11687099> (failed 1 times): 400 Bad Request
    2015-01-20 22:02:52+0900 [heny] DEBUG: Retrying <GET http://m.map.naver.com/siteview.nhn?code=11687099> (failed 2 times): 400 Bad Request
    2015-01-20 22:02:52+0900 [heny] DEBUG: Retrying <GET http://m.map.naver.com/siteview.nhn?code=11687099> (failed 2 times): 400 Bad Request
    2015-01-20 22:02:52+0900 [heny] DEBUG: Gave up retrying <GET http://m.map.naver.com/siteview.nhn?code=11687099> (failed 3 times): 400 Bad Request
    2015-01-20 22:02:52+0900 [heny] DEBUG: Crawled (400) <GET http://m.map.naver.com/siteview.nhn?code=11687099> (referer: None)
    2015-01-20 22:02:52+0900 [heny] DEBUG: Ignoring response <400 http://m.map.naver.com/siteview.nhn?code=11687099>: HTTP status code is not handled or not allowed
    2015-01-20 22:02:52+0900 [heny] DEBUG: Gave up retrying <GET http://m.map.naver.com/siteview.nhn?code=11687099> (failed 3 times): 400 Bad Request
    2015-01-20 22:02:52+0900 [heny] DEBUG: Crawled (400) <GET http://m.map.naver.com/siteview.nhn?code=11687099> (referer: None)
    2015-01-20 22:02:53+0900 [heny] DEBUG: Ignoring response <400 http://m.map.naver.com/siteview.nhn?code=11687099>: HTTP status code is not handled or not allowed
    2015-01-20 22:02:53+0900 [heny] INFO: Closing spider (finished)
    2015-01-20 22:02:53+0900 [heny] INFO: Dumping Scrapy stats:



    # -*- coding:euc-kr -*
    from scrapy.spider import BaseSpider
    from scrapy.selector import HtmlXPathSelector
    from scrapy.item import Item, Field

    class Website(Item):
    name = Field()
    description = Field()
    url = Field()

    class DmozSpider(BaseSpider):
    name = "heny"
    allowed_domains = ["map.naver.com"]
    start_urls = [
    "http://m.map.naver.com/siteview.nhn?code=11687099",
    "http://m.map.naver.com/siteview.nhn?code=11687099",
    ]

    def parse(self, response):
    hxs = HtmlXPathSelector(response)


    sites = hxs.select('//ul[@class="li1 li15" id="commentItems"]/li')
    items = []

    for site in sites:
    item = Website()
    item['name'] = site.select('a/text()').extract()
    item['url'] = site.select('a/@href').extract()
    item['description'] = site.select('text()').re('-\s([^\n]*?)\\n')
    items.append(item)

    return items

    • addr | edit/del BlogIcon 악당잰 2015.01.21 00:29 신고

      저도 파이썬이나 scrapy는 경험이 많이 없어서 도움이 될진 모르겟지만 제가 원인을 찾아본 바로는 서버에서 불필요한 반복 액서스를 방지하기 위해 쿠키값을 체크하여 에러가 나는경우가 있다네요. scrapy에 쿠키값을 false로 설정하는게 있다니 한번 시도해 보세요.

  3. addr | edit/del | reply 이진희 2015.10.19 11:51

    from string import join
    from article.items import ArticleItem

    class ArticleSpider(scrapy.Spider):
    name = "article"
    allowed_domains = ["http://joongang.joins.com"]

    start_urls = ["http://news.joins.com/politics",
    "http://news.joins.com/society",
    "http://news.joins.com/money",]

    def parse(self, response):
    sel = scrapy.Selector(response)
    urls = sel.xpath('//div[@class="bd"]/ul/li/strong[@class="headline mg"]')
    items = []

    for url in urls:
    item = ArticleItem()
    item['url'] = url.xpath('a/@href').extract()
    item['url'] = "http://news.joins.com"+join(item['url'])
    items.append(item['url'])

    for itm in items:
    yield scrapy.Request(itm,callback=self.parse2,meta={'item':item})

    def parse2(self, response):
    item = response.meta['item']
    sel = scrapy.Selector(response)

    articles = sel.xpath('//div[@id="article_body"]')
    items = []
    for article in articles:
    item['article'] = article.xpath('text()').extract()
    items.append(item['article'])
    return items


    안녕하세요! 스크래피 관련 질문드립니다.
    스파이더 코드 부분인데요 parse 함수에서 뉴스기사 url을 클로링하고 items리스트에 저장해서
    parse2에 request로 해당 url을 보내서 parse2함수에서는 기사를 크롤링하는 겁니다. 그런데 두 함수를 따로 구현해서 크롤링해보면 다 스크랩이 되는데 저렇게 한 스파이더에 두가지 함수를 넣어서하면 yield에서 request가 보내지지 않아서 그런지 크롤링이 되지 않더라구요...혹시 이코드의 문제점을 아시겠다면 답변 좀 부탁드립니다.

  4. addr | edit/del | reply 확인요청 2015.11.16 02:31

    Traceback (most recent call last):
    File "C:/Users/KeMi/PycharmProjects/townpage/townpage/spiders/townpage.py", line 7, in <module>
    from ..items import TownpageItem
    ValueError: Attempted relative import in non-package

    이런 에러가 발생하네요. items가 item.py파일에 있는걸 가져오는것 같은데....안되네요

    어떤식으로 수정이 들어가야하나요?

개인적으로 만들 앱에 필요한 데이타를 크롤링해야 해서 뭔가 간단하게 되는게 없을까 구글링 하던중 Scrapy라는 놈을 알게 되었다. 

생각보다 한글자료가 많이 없었기 때문에 혹시 scrapy를 사용하려는 개발자들이 내가 했던 삽질을 되풀이하게 하지 않기 위해 메모형식으로 남겨두려고 한다. 얼마나 도움될지는 모르지만...어쨋뜬!

먼저 첫번째로 환경설정에 대해 설명하고 두번째글에선 간단하게 사용하는 방법에 대하여 설명할까 한다. 

(본인도 파이썬은 리얼초보이므로 내용이 틀릴수 있으므로 저보다 더 잘 아시는 분께서는 지적 부탁드립니다.)


자..그럼..


개인적 사정상 윈도우환경과 맥환경을 동시에 설정을 해보았는데 이글에선 여기선 맥에 대해서만 설명하려고 한다.

윈도우설정하실분들은 구글폭풍검색으로 해결하시길..(본인의 경우 파이썬에 대한 기본지식이 없었던 탓에 윈도우환경설정은 꼬박 하루걸렸다능..

그에비해 맥환경에서는 한시간만에 샘플코드까지 실행성공할 수 있었다. 



기본적으로 아래의 링크(scrapy 공식홈페이지)를 따라 설치하면 된다. (물론 영어임.ㅠ ㅠ )

http://doc.scrapy.org/en/latest/intro/install.html


먼저 맨 상단에 scrapy를 설치하기위한 기본 설치항목들이 쭈욱 나열되있는데 꼭 설치를 해야한다. 이걸 하나라도 빼먹으면 scrapy가 동작하지 않을수 있다.


당연한 얘기지만 우선 파이썬 설치가 필요하다. 그러나 맥에서는 파이선은 2.7버전이 이미 들어가 있으므로 설치할 필요가 없다.

파이썬은 3.x버전까지 나와있는 상태이니 업데이트를 하고 싶은 본능이... 그러나 그건 쓸데없는 오지랍이다. scrapy의 지원버전이 2.7이므로 그냥 두시길..

혹시 모르니 터미널에서 파이썬 버전을 확인해 보자.


python —version


그다음 간단설치를 위한 setup-tool을 설치한다.

아래의 커맨드를 터미널에서 순서대로 실행하면 끝~


curl -O http://peak.telecommunity.com/dist/ez_setup.py

sudo python ez_setup.py

easy_install --help



또하나 설치에 필요한 pip를 설치해야 한다. 다른데에서도 많이 사용하는 모듈이므로 혹시 모르니 설치되어 있는지부터 확인해본다.

pip —version


설치가 안되어 있다면 설치 고고~

easy_install pip


이것도 필요한 라이브러리란다. 간단하게 pip로 설치.

pip install lxml


openssl도 필요하다니 설치한다.

easy_install PyOpenSSL

easy_install PyCrypto


이제 기본준비가 끝났으므로 주인공인 scrapy를 설치한다.

pip install Scrapy




설치 끝!



하지만 제대로 설치가 되어 있는지를 확인해 봐야 하므로 몇가지 scrapy 커맨드를 실행해 봐야 한다.

우선 hoge라는 프로젝트 생성해 보자.

scrapy startproject hoge


hoge라는 폴더가 생기고 몇가지 파일들이 에러없이 생성되면 설치가 제대로 된것이다.



크롤링이 제대로 되는지를 해봐야 하는데 만드는데 시간이 걸리므로 샘플소스를 다운받아서 실행해 보자.

https://github.com/scrapy/dirbot/archive/master.zip



dmoz라는 프로젝트가 만들어져 있는데 해당폴더로 이동하여 아래의 커맨드를 입력해 보자.

scrapy crawl dmoz -o items.json


뭔가 실행이 되고 items.json파일이 생기고 거기에 데이타가 들어가 있다면 crawling이 제대로 된다는 뜻이다.


그럼 이번글은 여기까지...


Posted by 악당잰 트랙백 0 : 댓글 3

댓글을 달아 주세요

  1. addr | edit/del | reply 김선영 2015.06.18 21:35

    안녕하세요.

    웹싸이트 크롤과 관련한 블로그글 잘 보고 갑니다. 제가 이번에 웹싸이트를 크롤을 처음해봅니다만, 위에서 말씀하신대로, scrapy, 파이썬, pip 을 전부 다운을 받아서 실행을 해야 하는 건지요? 파이썬은 다운을 받았습니다만, 다른 2개도 다운을 받아야 크롤이 가능한지요? 아참, 저도 일본에서 유학생활을 하고 있습니다만, 너무 반갑네요^^

    • addr | edit/del BlogIcon 악당잰 2015.06.20 12:54 신고

      네 반갑네요^^. 제 경험으로 말하자면 전부 다 필요했습니다. 실행되는듯하다가 알수없는 에러가 나고 그랬거든요. 아마도 설치환경에 따라 다른것 같습니다.

  2. addr | edit/del | reply 해해 2015.07.05 00:45

    감사합니다!!ㅠㅅㅠ 감동!!


iOS스터디(KJ-CODE) 발표자료.




메모


컨테이너뷰에 있는 컨트롤러를 전환해주는 방법.

코드상에서의 순서


//추가한다면

[self addChildViewController:viewControllerB]; 

[self.view addSubview:viewControllerB.view];

viewControllerB.view.frame = //크기결정해줘야함.

[viewControllerB didMoveToParentViewController:self]; 


//삭제일경우

[viewControllerA willMoveToParentViewController:nil]; 

[viewControllerA.view removeFromSuperview];     

[viewControllerA removeFromParentViewController];


didMoveToParentViewController가 콜백이 아닌 직접호출일까? 그이유는 애니메이션처리 때문에.. 

애니메이션이 언제 끝날지 모르므로...



//toViewController를 컨테이너에 추가

[self addChildViewController:toViewController];


//from부터to로 화면전환시 트랜젝션을 실행

[self transitionFromViewController:fromViewController

                   toViewController:toViewController

                           duration:1.0

                            options:UIViewAnimationOptionTransitionNone

                         animations:^{

                             //애니메이션 처리가 필요하다면...

                         }

                         completion:^(BOOL finished) {

                             // 추가가 완료되면 명시적으로 호출

                             [toViewController didMoveToParentViewController:self];

                         }];



컨테이너뷰

https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/BasicViewControllers/BasicViewControllers.html#//apple_ref/doc/uid/TP40007457-CH101-SW1


라이프사이클문서

https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/RespondingtoDisplay-Notifications/RespondingtoDisplay-Notifications.html#//apple_ref/doc/uid/TP40007457-CH12-SW1


컨테이너뷰 설명

http://qiita.com/paming/items/d8a29d644c994ce60d6a

Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

스토리보드가 도입되면서 스토리보드상에서 콘트롤러와 콘트롤러를 연결해주기만 하면 전면전환이 되는 편리한 기능이 추가 되었다.

(모달,푸쉬 그리고 애니메이션 옵션등 약간의 설정이 필요하긴 하다.)


예전에 코드로 전부 써주어야 했던것이 스토리보드로 흡수되면서 기본적인 화면전환의 경우는 코드없이도 간단하게 구현이 가능하다.

물론 몇번의 마우스클릭의 수고는 필요.


만약 Modal, Push 형태 이외의 화면전환이 필요한 경우는 화면전환 전용클래스(UIStoryboardSegue)를 이용하여 정의(커스텀)해 줄수 있는데

화면전환에 관련된 코드가 세그에 정의됨으로써 보다 가독성도 높일 수 있다.


스토리보드 사용법에 대해서는 다른 블로그에도 많이 소개 되어 있으므로 세그에(Segue)를 커스텀하여 애니메애션을 주는 방법을 몇가지 소개햐려 한다.



우선 UIStoryboardSegue를 상속받는 세그에(Segue)클래스를 생성한다.


@interface KJCodeMenuSegue : UIStoryboardSegue


@end




정의에는 perform메소드를 재정의 해준다. 여기에서 애니메이션과 화면전환을 정의 해준다.



- Push, Pop 세그에 를 커스텀 할 경우 (modal의 경우도 마찬가지 방법으로 처리해도 된다.)


- (void)perform {

    

//하나의 클래스에 여러개의 처리를 위해 id 이용하여 분기해도 .

NSLog(@"%@", self.identifier);

//뷰콘트롤러 인스턴스 취득

    UIViewController *sourceViewController = (UIViewController *)self.sourceViewController;

    UIViewController *destinationViewController = (UIViewController *)self.destinationViewController;

    

    if (self.identifier isEqualToString:@"menuPush") {

        

        //화면전환 애니메이션

        [UIView transitionWithView:sourceViewController.navigationController.view

                          duration:0.5f

                           options:UIViewAnimationOptionTransitionCrossDissolve

                        animations:^{

                            //실제로 화면전환코드를 작성. 애니메이션은 겹치지 않도록 NO 설정

                            [sourceViewController.navigationController pushViewController:destinationViewController animated:NO];

                        }

                        completion:nil];

        

    }else if(self.identifier isEqualToString:@"menuPush") {

        

        //화면전환 애니메이션

        [UIView transitionWithView:sourceViewController.navigationController.view

                          duration:0.5f

                           options:UIViewAnimationOptionTransitionCrossDissolve

                        animations:^{

                            //실제로 화면전환코드를 작성. 애니메이션은 겹치지 않도록 NO 설정

                            [sourceViewController.navigationController popViewControllerAnimated:NO];

                        }

                        completion:nil];

        

    }

    

}



- 컨테이너뷰에서의 임베디드 세그에(embedSegue)를 커스텀할 경우


- (void) perform {

    

    NSLog(@"%@", self.identifier);

    

    UIViewController *containerViewController = (UIViewController *) self.sourceViewController;

    UIViewController *nextViewController = (UIViewController *) self.destinationViewController;

    UIViewController *currentViewController = (UIViewController *) containerViewController.currentViewController;

    

    //컨테이너 콘트롤러에 화면전환 뷰콘트롤러를 자식뷰콘트롤러로 추가한다.

    [containerViewController addChildViewController:nextViewController];

    // 현재뷰콘트롤러에 이동(삭제) 라는것을 통지한다.

    [currentViewController willMoveToParentViewController:nil];

    

    // 전환될 뷰콘트롤러와 현재 뷰콘트롤러의 크기를 조정한다.

    nextViewController.view.frame = currentViewController.view.frame;

    

    // 화면전환 애니메이션 정의한다.

    [containerViewController transitionFromViewController:currentViewController

                                         toViewController:nextViewController

                                                 duration:0.1f

                                                  options:UIViewAnimationOptionTransitionCrossDissolve

                                               animations:^{

                                                   

                                               }

                                               completion:^(BOOL finished) {

                                                   //컨터이너뷰 안에서의 화면전환처리를 정의한다.

                                                   containerViewController.currentViewController = nextViewController;

                                                   [currentViewController removeFromParentViewController];

                                                   [nextViewController didMoveToParentViewController:containerViewController];

                                               }];

    

}



Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

iPhone 캘린더에 등록된 내용을 보여주거나 표시해주기 위해서는 EventKit을 이용하면 된다. 캘린더 이외에 리마인더도 제어 할 수 있으나 여기선 캘린더 등록 위주로 간단히 설명할 생각이다.



사실 애플도큐먼트만 봐도 굳이 설며이 필요없는 간단한 내용이지만 중요한 포인트는 

EventKit을 사용할 클래스는 싱글톤 클래스로 만들어 사용할 것이라는 내용.

(도큐먼트에 보면 로드하는데 시간이 걸리므로 로드 후 사용하는것이 좋다고 소개되어 있다.)

그러므로 일단 여기서도 싱글톤으로된 매니져클래스를 샘플로 만들어 보았다.


자세한 사용법은 아래의 링크를 참고하면 된다.(일본어임)

https://developer.apple.com/jp/devcenter/ios/library/documentation/EventKitProgGuide.pdf



본론으로 들어가서,

물론, EventKit, EventKitUI 두개의 프레임워크를 먼저 추가해 주어야 한다.

(이름만 봐서도 알겠지만 EventKitUI는 이벤트 편집을 위한 UI프레임워크이다.)






먼저 EventManager.h

필요한 이벤트킷을 임포트 해준 다음 싱글톤 인스턴스 메소드(sharedInstance)와 이벤트 검색을 위한 메소드(fetchEvent...) 그리고 이벤트 추가, 수정을 위한 화면표시용 메소드(eventEditController...)를 선언해 준다.



#import <EventKit/EventKit.h>

#import <EventKitUI/EventKitUI.h>


@interface EventManager : NSObject


+ (EventManager *)sharedInstance;


- (NSArray *)fetchEvent;

- (NSArray *)fetchEventStartDate:(NSDate *)startDate EndDate:(NSDate *)endDate;

- (void)eventEditController:(id)owner event:(EKEvent *)event;


@end




그다음 EventManager.m

싱글톤 클래스를 구현해 주기 위한 메소드들을 추가해 준 다음 헤더파일에 선언된 메소드를 정의해 준다.

그리고 이벤트정보에 접근하기 위한 이벤트스토어를 프로퍼티(EKEventStore)로 선언.

마지막으로 이벤트 추가, 수정을 위한 EventKitUI를 사용하기 위한 프로토콜을 명시해 준다.



@interface EventManager() <EKEventEditViewDelegate>


@property (nonatomicstrongEKEventStore *store;


@end



@implementation EventManager 


static EventManager *_sharedInstance;


#pragma mark - singleton

+ (EventManager *)sharedInstance

{

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        _sharedInstance = [[self alloc] init];

    });

    

    return _sharedInstance;

    

}


+ (id)allocWithZone:(NSZone *)zone {

    

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        _sharedInstance = [super allocWithZone:zone];

    });

    

    return _sharedInstance;

    

}


- (id)init {

    self = [super init];

    if (self) {

        _store = [[EKEventStore alloc] init];

        

        if([EKEventStore authorizationStatusForEntityType:EKEntityTypeReminder] == EKAuthorizationStatusAuthorized) {

            //접근거부

        } else {

            EKEventStore *eventStore = [[EKEventStore alloc] init];

            [eventStore requestAccessToEntityType:EKEntityTypeReminder

                                       completion:^(BOOL granted, NSError *error) {

                                           if(granted) {

                                               // 처음 접근시 대화장자가 표시되는 경우 OK버튼을 누르면 여기로 들어옴

                                           } else {

                                               dispatch_async(dispatch_get_main_queue(), ^{

                                                   //접근권한 없음

                                                   UIAlertView *alert = [[UIAlertView alloc]

                                                                         initWithTitle:NSLocalizedString(@"This app does not have access to you reminders.", nil)

                                                                         message:NSLocalizedString(@"To display your reminder, enable [YOUR APP] in the \"Privacy\" → \"Reminders\" in the Settings.app.", nil)

                                                                         delegate:nil

                                                                         cancelButtonTitle:NSLocalizedString(@"OK", nil)

                                                                         otherButtonTitles:nil];

                                                   [alert show];

                                               });

                                           }

                                       }

             ];

        }

    }

    

    return self;

}


- (id)copyWithZone:(NSZone *)zone

{

    return self;

}


- (void)dealloc {

    

}


#pragma mark - Event fetch

- (NSArray *)fetchEvent {

    

    //기본값은 일주일로 잡아준다.

    

    NSCalendar *calendar = [NSCalendar currentCalendar];


    // 시작시간 작성

    NSDateComponents *oneWeekAgoComponents = [[NSDateComponents alloc] init]; oneWeekAgoComponents.day = -7;

    NSDate *oneDayAgo = [calendar dateByAddingComponents:oneWeekAgoComponents

                                                  toDate:[NSDate date]

                                                 options:0];

    // 종료시간 작성

    NSDateComponents *oneYearFromNowComponents = [[NSDateComponents alloc] init]; oneYearFromNowComponents.year = 1;

    NSDate *oneYearFromNow = [calendar dateByAddingComponents:oneYearFromNowComponents

                                                       toDate:[NSDate date]

                                                      options:0];

    

    return [self fetchEventStartDate:oneDayAgo EndDate:oneYearFromNow];

    

}


- (NSArray *)fetchEventStartDate:(NSDate *)startDate EndDate:(NSDate *)endDate {

    

    // 조건생성

    NSPredicate *predicate = [_store predicateForEventsWithStartDate:startDate

                                                            endDate:startDate

                                                          calendars:nil];

    // 조건에 따른 패치 실행.

    return [_store eventsMatchingPredicate:predicate];

    

}


#pragma mark - Event editing controller

- (void)eventEditController:(id)owner event:(EKEvent *)event {


    //event가 nil 일 경우에는 신규이벤트 등록이 된다.

    

    EKEventEditViewController *eventEditViewController = [EKEventEditViewController new];

    eventEditViewController.editViewDelegate = self;

    eventEditViewController.event = event;

    eventEditViewController.eventStore = _store;

    

    [owner presentViewController:eventEditViewController animated:YES completion:nil];

}


#pragma mark - EKEventEditViewDelegate

- (void)eventEditViewController:(EKEventEditViewController *)controller didCompleteWithAction:(EKEventEditViewAction)action

{

//UI에서 이벤트를 받을경우 처리할 딜리게이트 메소드.


NSError *error = nil;

switch (action) {

case EKEventEditViewActionCanceled:

break;

case EKEventEditViewActionSaved:

[controller.eventStore saveEvent:controller.event span:EKSpanThisEvent error:&error];

break;

case EKEventEditViewActionDeleted:

[controller.eventStore removeEvent:controller.event span:EKSpanThisEvent error:&error];

break;

default:

break;

}

    

[controller dismissViewControllerAnimated:YES completion:nil];

}


@end



기술한 샘플코드는 전혀 테스트를 하지 않았기 때문에 동작안할 수도 있으므로 참고만 하시길...






Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

日本語練習のため書いたブログ記事をも日本語にしてみた。

原本はこちら。

http://dolfalf.tistory.com/admin/entry/post/?id=98



普段、法則というとどんな意味が表すのか。簡単にいうとある条件で例外なく、決まった回答が得られる公式のようなものくらいでしょう。

そしたら1万時間の法則というタイトルは1万時間をどこかに費やしたら何かが得られる気がする。



本で紹介している1万時間の法則は「誰でも1日3時間ずつ10年間ばればその分野で専門家になる」というのを主張している。(あんまり「誰でも」というところは悲観的だが一旦、論外として。)



それなりにプログラミングを10年以上書いている私は、本の内容だと既に専門家になるのは十分なはずだ。

単純に1日8時間(実際にはもっと多いが)で計算してみても

(365日- 70(休日)) * 10年 * 8時間 = 約23,600時間



しかし。。。いくら時間を費やしながら頑張っても専門家になれない理由がある。

練習もただの練習ではだめらしく、集中することが最も重要だそうだ。

本で紹介されているエピソードでこんな話がある。

ある教え子が「1つの曲を上手に演奏するためにはいくら練習するのがいいですか。」の質問について

「何も考えず練習したら一日中練習しても足りないが一つ一つ集中して練習すれば2、3時間で充分だ。」と答えた。

 


これから集中した1万時間をカウントし直さないと。


Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요