블로그 이미지
매몰

모바일 어플리케이션 개발 1인 기업 고영진모바일입니다. 저와 함께 맛깔스러운 앱을 만들어 보아요~

Rss feed Tistory
개발/android 2019.01.17 17:27

EditText에 자동으로 단위 콤마 넣기



NumberFormat 을 이용하면 숫자에 단위 콤마를 쉽게 찍을수 있다.


하지만 이를 EditText에 적용할려고 하면 한가지 문제가 생긴다.


입력할때마다 콤마가 찍혀야 하는데...

addTextChangedListener에서 NumberFormat으로 콤마를 찍으면 당연히 텍스트가 변경되므로 다시 리스너가 호출된다. 즉, 호출이 무한 반복되면서 앱이 멈추게 된다.


그래서 살짝 꼼수를 부려봤다.


EditText 밑에 같은 크기의 TextView를 깔고 여기에 콤마를 찍은 텍스트를 대신 써주고,

후에 EditText를 동기화 하는 것이다. 물론, EditText는 투명하게 설정해 안보이게 한다.


사실 임시 방편으로 만든것인데.. 생각보다 잘 작동해 계속 쓰고 있다 ㅎㅎㅎ



소스는 다음과 같다. 요즘 뜨고 있다는 코틀린으로 작성했다~


class NumberEditText: RelativeLayout {

private var editText: EditText
private var textView: TextView

init {
editText = EditText(context)
textView = TextView(context)

initBase()
}

constructor(context: Context): super(context) {
editText = EditText(context)
textView = TextView(context)

initBase()
}

private fun initBase() {
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)

//EditText가 안보이도록 투명 처리한다
editText.setTextColor(Color.TRANSPARENT)
editText.setBackgroundColor(Color.TRANSPARENT)
editText.gravity = gravity

textView.gravity = gravity

//TextView 위에 EditText를 생성한다
addView(textView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
addView(editText, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))

editText.setOnTouchListener { view, motionEvent ->
//EditText를 터치하면 TextView의 내용을 동기화 한다
if (motionEvent.action == MotionEvent.ACTION_UP) {
//내용을 쓸때 입력 커서 위치가 변경되지 않도록 조정한다
val length = textView!!.text.length - (view as EditText).text.length
val selectionStart = view.selectionStart + length
val selectionEnd = view.selectionEnd + length

//내용을 쓰고 커서 위치를 조정한 후 적용되도록 포커스를 요청한다
view.setText(textView?.text)
view.setSelection(selectionStart, selectionEnd)
view.requestFocus()
}

false
}

editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {

}

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {

}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
//콤마를 찍어 TextView에 쓴다
textView.text = getNumberText(s.toString())
}
})
}

private fun getNumberText(text: String): String {
return if (text.length > 1) NumberFormat.getNumberInstance().format(text.replace(",", "").toDouble()) else text
}
}



수제앱장인


 

 

CEO

Developer

S/W Enginner

고영진


실패만 하고 있어도 꿈을 포기하지 않는 남자
제가 직접 경험하고 습득한 지식을 위주로 올릴게요

사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/php, javascript 2018.11.26 10:09

round()를 5의 배수로도 반올림 해보자



왜 반올림은 0으로만 할까?

0말고 다른수로도 반올림 해보는건 어떨까?


라는 생각을 가끔씩 해봤는데.. 솔직히 별 필요성을 느끼지 못했었다.

그런데 주식 관련한 로보어드바이저를 만들면서 필요해졌다. 호가를 찍을때 5단위가 되기 때문이다.


아래와 같이 php의 반올림 함수인 round()를 이용하여 5의 배수로 반올림 해주는 코드를 짜봤다.


원리는 굳이 설명안해도 될만큼 간단하다. 여기서 볼것은 round() 함수의 두번째 인수인 반올림 자릿수도 고려한다는 사실이다. 이러한 편리한 기능은 살려놔야 코딩하는 재미가 있다ㅎㅎ




function roundCenter($price, $precision) {

//지정한 자릿수로 반올림 (1)

$round = round($price, $precision);

//지정한 자릿수 이하가 5가 되도록 변경 (2)

$square = -$precision;

$digit = pow(10, $square);

$center = (floor($price / $digit) * $digit) + (5 * pow(10, $square -1));

//(1)과 (2)를 원래 숫자와 비교하여 더 가까운 것을 택함

return abs($price - $round) < abs($price - $center) ? $round : $center;

}




결과값으로 검증해 보자...


echo roundCenter(573424.2517, 3).'<br />';

echo roundCenter(573424.2517, 2).'<br />';

echo roundCenter(573424.2517, 1).'<br />';

echo roundCenter(573424.2517, 0).'<br />';

echo roundCenter(573424.2517, -1).'<br />';

echo roundCenter(573424.2517, -2).'<br />';

echo roundCenter(573424.2517, -3).'<br />';




출력...


573424.2515
573424.25
573424.25
573424.5
573425
573400
573500




위에서 보듯이 지정 자릿수가 3이면 소수점 3째짜리까지 나오거나 소수점 4째자리가 5인 수가 나온다.




수제앱장인


 

 

CEO

Developer

S/W Enginner

고영진


실패만 하고 있어도 꿈을 포기하지 않는 남자
제가 직접 경험하고 습득한 지식을 위주로 올릴게요

사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/android 2018.09.27 13:30

sqlite의 time이 자정을 인식 못할 경우, 간단한 꼼수 해결법



안드로이드의 sqlite 데이터베이스를 사용하는 도중 이상한 점을 하나 발견했다.


time 데이터타입에서 00시 또는 24시, 즉 자정일때 time() 함수가 먹히질 않는다는 점이다.


예를 들면,

(time 데이터타입의 field1 시각 +  int 데이터타입의 field2 초) > 특정시각


을 비교할때


SELECT * FROM table1 WHERE time(field1, field2 ||' seconds') > 23:59:30

와 같이 사용한다.


하지만 이때, field1이 23:59:00 이고 field2가 60초 이상이면 자정이 넘어가면서 SELECT 결과가 항상 아무것도 안나오게 된다. field1이 24:00:00 이상이어도 마찬가지다.


이걸 해결하기 위해 만방으로 살펴봤지만... 역시 코딩은 꼼수가 최고다.


그냥... 자정이 넘어갈것 같으면 시간을 앞당겨주면 된다ㅎㅎ



다음과 같이 해보자~

private String getTime(String time, String addtime) {
return "time('" + time + "'" + addtime + ")";
}

private String getTime(String field, String addfield, String addtime) {
return "time(" + field + "," + addfield + "||' seconds'" + addtime + ")";
}

private Cursor fetch(SQLiteDatabase db, String time) {
//특정시각 time이 21시보다 크면 3시간을 앞당겨 준다.
String addText = Integer.parseInt(time.substring(0, 2)) < 22 ? "" : ",'-3 hours'";

return db.rawQuery("SELECT * FROM table1 WHERE " + getTime("field1", "field2", addText) + "<" + getTime(time, addText), null);
}

자정이 넘어 갔을때 시간을 앞당겨 비교해 주면 정상적으로 작동한다. 


수제앱장인


 

 

CEO

Developer

S/W Enginner

고영진


실패만 하고 있어도 꿈을 포기하지 않는 남자
제가 직접 경험하고 습득한 지식을 위주로 올릴게요



사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/ios 2018.07.30 15:07

스레드에서 반복문으로 UI를 변경할때 주의할점



내가 만든 스레드에서 UI를 바꿀수 없다는것은 누구나 알것이다. 물론 젖먹이 개발자 시절에 난 몰랐다...


어잿든 내 스레드에서 UI를 바꿀려면 메인스레드를 호출해야 한다. ios 에서는 DispatchQueue.main.async 이 그 역활을 한다.


짐작했겠지만 메인스레드는 새로 만들어지는게 아니므로 당연히 동기적으로 움직인다. 


최근까지도 이 사실을 자주 망각했다. 바로 반복문에서 메인스레드를 호출할때였다.


내 스레드에서 메인스레드를 반복문으로 돌릴때는 서로가 따로 놀기 때문에 (내 스레드와 메인스레드가 서로 비동기라서) 메인스레드에서 UI가 아직 변경되지도 않았는데 내 스레드에서 또 호출해 버린다. 이렇게 되면 버벅거리는 현상이 발생한다.


테스트할때서야 버벅거림을 발견하고 짜증내다 아... 하고 숙연해 진적이 한둘이 아니었다.


아래를 보면 스레드에서 ui를 10번 갱신한다.

하지만 apply변수를 뺀다면 10번이 다 갱신 되지 않고 중간에 몇번은 건너뛰어 결국 일부만 실행되는것 처럼 보일것이다.



//스레드 시작

Thread(target: self, selector: #selector(runLoop), object: nil).start()



//스레드 실행 함수

@objc func runLoop() {

    var apply = true

    var count = 0

        

    while count < 10 {

        //메인스레드에서 작업이 완료되었을때만 실행

        if apply {

            //시작

            apply = false

            count += 1

                

            DispatchQueue.main.async {

                //ui 변경

                //...

                setNeedsDisplay()

                    

                //완료

                apply = true

            }

        }

    }

}




쉽게 말해 동기화를 해야 한다는 말이다.


알면서도 실수할수 있으니 항상 유념하자.



수제앱장인


 

 

CEO

Developer

S/W Enginner

고영진


실패만 하고 있어도 꿈을 포기하지 않는 남자
제가 직접 경험하고 습득한 지식을 위주로 올릴게요





사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/android 2018.06.07 14:03

Intent 전달 방식을 모방하여 부모 activity를 호출하기


IOS에서는 prepare()을 통해서 부모 controller를 쉽게 전달할 수 있다.

하지만 Android에서는 Intent로 activity를 전달하기 쉽지 않다.


뭐, Intent 말고도 여러 전달 방식이 있기 때문에 상관없지만...

깔끔하게 코딩하고 싶은 마음에 함 만들어 보았다.


static을 이용하지만, 마치 Intent로 전달하는것 같은 느낌으로ㅎㅎ



public class BaseActivity extends FragmentActivity {

private static final String KEY_PARAM_CONTEXT_ID = "ParamContextId";

//전달할 Context를 담는 Map
private static HashMap<Long, Context> mParamContextMap = null;

//전달된 Context를 저장할 인스턴스
private Context mParamContext;

//부모 Activity에서 호출되어 Context를 받는다
public static void putParamContext(Intent intent, Context context) {

//Map이 널이면 즉 Context가 하나도 없다면 생성한다
if (mParamContextMap == null)
mParamContextMap = new HashMap<>();

//현재 시간을 식별자로 전달 Intent에 저장한다. 이는 Context가 static에 담아지기 때문에 상속받은 모든 activity에서 만약에 있을 출동을 막기 위함이다.
long id = System.currentTimeMillis();
intent.putExtra(KEY_PARAM_CONTEXT_ID, id);

//위의 식별자를 해당 키로 하여 Map에 Context를 담는다.
mParamContextMap.put(id, context);
}

//부모 Activity의 Context를 반환한다.
protected Context getParamContext() {
return mParamContext;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//전달된 Context가 있다면 실행한다.
if (mParamContextMap != null) {
long id = getIntent().getLongExtra(KEY_PARAM_CONTEXT_ID, 0);
if (id != 0) {
//Context를 인스턴스에 저장하고 static Map에서는 삭제한다.
mParamContext = mParamContextMap.get(id);
mParamContextMap.remove(id);
}

//Map에 아무것도 없다면 널로 풀어준다.
if (mParamContextMap.size() == 0)
mParamContextMap = null;
}
}
}




부모 Activity에서 자식 Activity 호출...


Intent intent = new Intent(this, MyActivity.class);
BaseActivity.putParamContext(intent, this);
startActivity(intent);



자식 Activity에서 부모 Activity 호출...


public class MyActivity extends BaseActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

    Context parentContext = getParamContext();

//부모 Context롤 이용하여 작업한다.

//

//

}

}


 

 



수제 앱 장인: 고영진


(주)고영진모바일

1인기업 대표이사 겸 개발자

  

     실패만 하고 있어도 꿈을 포기하지 않는 남자 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요

 






사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/ios 2018.05.08 11:12

UIEdgeInsets 없이 UILabel에 여백 넣기

UILabel에 여백을 넣는 방법은 다양하다.

그 중 NSLayoutConstraint를 이용하는 법을 다뤄보자!






class FitLabel: UILabel {


    //상하좌우 여백 storyboard 입력값

    @IBInspectable var edge: CGSize = CGSize(width: 0, height: 0)

    

    //너비, 높이가 적용될 Constraint

    private var mWidthConstraint, mHeightConstraint: NSLayoutConstraint?

    

    //좌우 여백값

    var edgeAllWidth: CGFloat {

        return edge.width * 2

    }

    

    //상하 여백값

    var edgeAllHeight: CGFloat {

        return edge.height * 2

    }

    

    override func awakeFromNib() {

        super.awakeFromNib()

        

        //storyboard에서 초기화된 글자로 맞춤

        fit()

    }


    func set(fitText text: String) {

        //코드상에서 글자를 직접 넣어 맞춤

        self.text = text

        fit()

    }

    

    private func fit() {

        //글자 + 여백 크기

        let width = intrinsicContentSize.width + edgeAllWidth

        let height = intrinsicContentSize.height + edgeAllHeight


        if mWidthConstraint == nil && mHeightConstraint == nil {

            //첫 실행때만 Constraint를 추가

            

            //Constraint를 직접 추가하기 위해 오토사이징 끔

            translatesAutoresizingMaskIntoConstraints = false

            

            //위의 글자 + 여백 크기를 너비,높이 Constraint로 생성하고 추가

            let widthConstraint = NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: width)

            let heightConstraint = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: height)

            addConstraint(widthConstraint)

            addConstraint(heightConstraint)

            mWidthConstraint = widthConstraint

            mHeightConstraint = heightConstraint

        }

        else {

            //두번째부터는 Constraint의 수치만 변경

            mWidthConstraint!.constant = width

            mHeightConstraint!.constant = height

        }

        

        //중앙 정렬

        textAlignment = .center

    }

}




핵심 원리는 그저 글자 전체크기를 불러와 여백을 더하여 NSLayoutConstraint를 만드는 것이다.

너무 간단해서 더이상 설명이 필요없다ㅎㅎ


 

 



수제 앱 장인: 고영진


(주)고영진모바일

1인기업 대표이사 겸 개발자

  

     실패만 하고 있어도 꿈을 포기하지 않는 남자 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요

 






사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/ios 2018.03.26 10:42

손쉽게 아이폰 앱을 새로 실행 시키는 코드 한줄



안드로이드에서는 메인 activity를 종료하고 다시 호출하면 바로 재시작이 된다.


하지만 아이폰에서는 그렇게 할수가 없었다. 혹시나 하는 마음에 재시작 시키는 함수를 찾아봤지만 이 또한 없었다. 나의 구글링 실력이 부족해서 못찾은거일수도 있지만... 좌절하고 있는 순간...


정말 우연하게 방법을 알아냈다. 사실 실수로 코드를 잘못 넣었는데... 재시작이 되는것이다... 아! 실수로 찍은 로또나 될것이지.. 어쨋든 기분은 좋다. 불로소득같은 느낌이다.



방법은 진짜 간단하다.

AppDelegate 의 applicationWillEnterForeground 에서 exit(0)를 호출해주면 된다.

아마도 잠에서 깨어나기 직전에 종료를 해주면 깨어나는 작업이 중단되지 않고 계속 진행되면서 재시작되는 효과가 나오는것 같다.


단점은 내가 원하는 시점이 아니라 앱이 깨어나는 순간에서만 적용된다는 것이다.

하지만 이는 아주 유용하게 쓰일수 있다.


iOS 특성상 앱을 완전 종료하지 않고 임시로 백그라운드로 내렸다가 다시 불려오는 경우가 많다.

(폰 아래 버튼은 종료 버튼이 아니라 홈 버튼이기 때문에)

즉, 앱이 완전 종료되고 다시 실행되어야만 하는 경우 매우 기특하게 사용될수 있다.



다음은 1시간 동안 앱이 포어그라운드로 깨어나지 않았다면 새로 실행 시키는 코드이다.

참고하여 다양하게 사용하길 바란다.



class AppDelegate: UIResponder, UIApplicationDelegate {


    let RESTART_TIME: TimeInterval = 3600

  

    var mResignTime: TimeInterval?


    func applicationWillResignActive(_ application: UIApplication) {

        mResignTime = NSDate().timeIntervalSince1970

    }


    func applicationWillEnterForeground(_ application: UIApplication) {

        if mResignTime != nil && NSDate().timeIntervalSince1970 - mResignTime! > RESTART_TIME {

            exit(0)

        }


    }

}



아이폰X와 아이폰7 (11.2)에서 테스트 해보았다. 잘 돌아간다. 굳!



 

 



수제 앱 장인: 고영진


(주)고영진모바일

1인기업 대표이사 겸 개발자

  

     실패만 하고 있어도 꿈을 포기하지 않는 남자 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요

 




사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/ios 2018.01.29 13:34

UIView의 원 테두리가 얇거나 작아보일때...



가끔 우리는 아주 사소한 차이로 고민할 때가 있다.


이거 왜이러니? 기분탓인가? 한번 살펴볼까? 귀찮다 이정도쯤이야 뭐..


하고 지나친적이 있지 않은가ㅎ 나는 많다ㅎㅎ

최근에도 그랬다.


UIView에서 StrokeEllipse 를 이용해 rect 크기 만큼 원 테두리를 그릴수 있다.

하지만 그려 놓고 나니 왠지 생각보다 작아보였고, 안에 색깔있는 원을 넣으니 테두리 굵기마저 얇아 보였다.. 뭐지???


처음엔 그냥 지나쳤다가 똥싸고 손안씻은것처럼 찝찝해서 한번 살펴보았다.

바보같았다... 왜 몰랐을까? 테두리 굵기는 양쪽으로 넓어진다는 사실을...

그래서 한쪽 굵기만큼 반이 얇아지고 작게 보이는 것이었다. 


아래와 같이 하면 금방 해결된다.. 혹시나 귀찮아서 그냥 지나칠 분들을 위해 글을 남긴다.




@IBInspectable var color: UIColor = UIColor.redColor()

@IBInspectable var borderColor: UIColor = UIColor.blackColor()

@IBInspectable var borderWidth: CGFloat = 2


override func drawRect(rect: CGRect) {

    let context = UIGraphicsGetCurrentContext()

        

    //테두리용 크기 얻기

    let inRect = strokeRect(rect, width: borderWidth)

        

    //색을 체운 그리기

    drawColor(context!, color: color, rect: inRect)

        

    // 테두리 그리기

    drawStroke(context!, width: borderWidth, color: borderColor, rect: inRect)

}

  

    

private func drawColor(context: CGContext, color: UIColor, rect: CGRect) {

    CGContextSetFillColorWithColor(context, color.CGColor)

    CGContextFillEllipseInRect(context, rect)

}

    

private func drawStroke(context: CGContext, width: CGFloat, color: UIColor, rect: CGRect) {

    CGContextSetLineWidth(context, width)

    CGContextSetStrokeColorWithColor(context, color.CGColor)

    CGContextStrokeEllipseInRect(context, rect)

}

    

private func strokeRect(rect: CGRect, width: CGFloat) -> CGRect {

    //굵기의 반만큼 rect 크기를 줄인다

    let width2 = width / 2

    return CGRectInset(rect, width2, width2)

}




 

 



수제 앱 장인: 고영진


(주)고영진모바일

1인기업 대표이사 겸 개발자

  

     실패만 하고 있어도 꿈을 포기하지 않는 남자 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요

 





사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/ios 2017.11.20 11:45

NSString을 마치 UILabel인 양 사용하기

UILabel을 추가하면 간단하게 글자를 넣을 수 있다.

하지만 sub view가 많아지면 어플이 무거워 질 수 있는데 이럴때에는 NSString을 써주면 좋다.


그렇다면 NSString를 UILabel처럼 배경색을 넣어서 마치 view인듯 사용해 보자~


 


먼저, NSString이 들어있는 클래스를 하나 만들자! NSString를 상속받아 확장해도 좋다. 나는 그냥 했다. TextBox라고 명명하겠다.


class TextBox {

    

    //정렬키 상수

    static let CENTER = 0

    static let LEFT = 8

    static let RIGHT = 16

    static let TOP = 1

    static let BOTTOM = 2

    

    var mText: NSString

    var mAlign: Int

    var mFontAttrDic: [String: NSObject]

    var mRect, mBgRect: CGRect

    var mPadding: (left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat)

    var mBgColor: UIColor

    var mCornerRadius: CGFloat

    

    init(text: NSString, align: Int, padding: (left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat), font: UIFont, color: UIColor, bgColor: UIColor, corner: CGFloat = 0) {

        mText = text

        mAlign = align  //정렬

        mFontAttrDic = [

            NSForegroundColorAttributeName: color,

            NSParagraphStyleAttributeName: NSMutableParagraphStyle(),

            NSFontAttributeName: font

        ]

        mRect = CGRect()    //글자 위치크기

        mBgRect = CGRect()  //배경 위치크기

        mPadding = padding  //글자와배경 간격

        mBgColor = bgColor  //배경색

        mCornerRadius = corner //둥근사각형 각크기

    }

    

    func setPoint(x: CGFloat, _ y: CGFloat) -> TextBox {

        //글자 원본크기

        let size = mText.sizeWithAttributes(mFontAttrDic)

        let width = size.width

        let height = size.height

        

        //크기 설정

        let padding = mPadding

        mRect.size.width = width

        mRect.size.height = height

        mBgRect.size.width = width + padding.left + padding.right

        mBgRect.size.height = height + padding.top + padding.bottom

        

        //위치 설정

        //가로 정렬

        switch mAlign >> 3 {

        case 1: //왼쪽 정렬

            mRect.origin.x = x + padding.left

            mBgRect.origin.x = x

            

        case 2: //오른쪽 정렬

            let x = x - padding.right - width

            mRect.origin.x = x

            mBgRect.origin.x = x - padding.left

            

        default:    //가운데 정렬

            let x = x - (width / 2)

            mRect.origin.x = x

            mBgRect.origin.x = x - padding.left

        }

        

        //세로 정렬

        switch mAlign & 7 {

        case 1: //위쪽 정렬

            mRect.origin.y = y + padding.top

            mBgRect.origin.y = y

            

        case 2: //아래쪽 정렬

            let y = y - padding.bottom - height

            mRect.origin.x = y

            mBgRect.origin.x = y - padding.top

            

        default:    //가운데 정렬

            let y = y - (height / 2)

            mRect.origin.y = y

            mBgRect.origin.y = y - padding.top

        }

        

        return self

    }

    

    func draw(context: CGContext) {

        //배경 출력

        CGContextSetFillColorWithColor(context, mBgColor.CGColor)

        

        if mCornerRadius == 0 {

            //사각형

            CGContextFillRect(context, mBgRect)

        }

        else {

            //둥근사각형

            CGContextAddPath(context, UIBezierPath(roundedRect: mBgRect, byRoundingCorners: .AllCorners, cornerRadii: CGSizeMake(mCornerRadius, mCornerRadius)).CGPath)

            CGContextFillPath(context)

        }

        

        //글자 출력

        mText.drawInRect(mRect, withAttributes: mFontAttrDic)

    }

}


위의 소스를 보면 알겠지만 미리 색이나 글자배경 간격등을 정해준 후 setPoint로 위치를 찍어주면 글자크기를 얻어 배경을 설정값에 맞게 만들어주고 draw로 그려준다.




자.. UIView를 확장하여 위의 TextBox를 이용해 보자


class TextBoxsView: UIView {


    var mTextBoxs: [TextBox]!

    

    func initTextBoxs() {

        //폰트

        let font = UIFont.systemFontOfSize(15)

        

        mTextBoxs = [TextBox]()

        

        //사각형 배경

        mTextBoxs.append(TextBox(text: "Rect", align: TextBox.CENTER, padding: (30, 50, 40, 10), font: font, color: UIColor.whiteColor(), bgColor: UIColor.blackColor()).setPoint(100, 200))

        

        //둥근사각형 배경

        mTextBoxs.append(TextBox(text: "RoundRect", align: TextBox.LEFT, padding: (30, 30, 30, 30), font: font, color: UIColor.greenColor(), bgColor: UIColor.blueColor(), corner: 10).setPoint(100, 400))

    }

    

    override func drawRect(rect: CGRect) {

        let context = UIGraphicsGetCurrentContext()

        

        //출력

        for textBox in mTextBoxs {

            textBox.draw(context!)

        }

    }

}


테스트를 위해 설정값이 틀린 사각형과 둥근사각형 둘을 만들었다. initTextBoxs를 호출하면 된다.




끝으로 ViewController에서 view를 위의 TextBoxsView로 바꾸고 호출한다.


class TextBoxViewController: ViewController {

    

    override func viewDidLoad() {

        super.viewDidLoad()

        

        (view as! TextBoxsView).initTextBoxs()

    }

}





결과♡





 

 



수제 앱 장인: 고영진


(주)고영진모바일

1인기업 대표이사 겸 개발자

  

     실패만 하고 있어도 꿈을 포기하지 않는 남자 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요

 




사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
개발/android 2017.09.11 10:17

ListView에 EditText를 넣을때 반드시 살펴봐야 할점

설정 페이지를 만들때 ListView로 EditText를 넣고 싶은 유혹에 빠진다


하지만 ListView의 특성상 사실 EditText는 적합하지 않다.

ListView는 각각의 row뷰를 재사용하여 메모리를 아끼기 때문에 

EditText의 변경값을 일일이 저장해 두었다가 다시 불려줘야 한다.


그리고 이때 진짜 주의할게 하나 있다.

변경 리스너의 중복을 방지하는 것이다. 반드시 재사용 뷰의 예전 리스너를 지워줘야 한다.

안그러면 예전 목록들과 뒤죽박죽 되버린다.



이해를 돕기 위해 리스너를 지우지 않았을때와 지웠을때도 비교해 보았다.


public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

//리스트 목록 넣기
String[] names = getResources().getStringArray(R.array.list_item);
ArrayList<Item> list = new ArrayList<>();

int length = names.length;
for (int i = 0 ; i < length ; i++)
list.add(new Item(names[i]));

((ListView)findViewById(R.id.ItemListView)).setAdapter(new ItemAdapter(this, R.layout.list_row, list));
}

//리스트 목록 클래스
private class Item {
public String mName, mValue;
public TextWatcher mTextWatcher;

public Item(String name) {
mName = name;
mValue = "";

//EditText 변경 리스너 생성
mTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//변경된 값을 저장한다
mValue = s.toString();
}

@Override
public void afterTextChanged(Editable s) {

}
};
}
}

private class ItemAdapter extends ArrayAdapter<Item> {

private int mResource;

public ItemAdapter(Context context, int resource, List<Item> list) {
super(context, resource, list);
mResource = resource;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout itemView;

if (convertView == null) {
itemView = new LinearLayout(getContext());

LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
vi.inflate(mResource, itemView, true);
}
else
itemView = (LinearLayout) convertView;

Item item = getItem(position);

((TextView)itemView.findViewById(R.id.NameTextView)).setText(item.mName);

EditText editText = (EditText)itemView.findViewById(R.id.ValueEditText);

//예전 리스너를 삭제한다
clearTextChangedListener(editText);

//값을 불려오고 해당 리스너를 적용한다
editText.setText(item.mValue);
editText.addTextChangedListener(item.mTextWatcher);

return itemView;
}

private void clearTextChangedListener(EditText editText) {
//리스트 목록의 모든 리스너를 대상으로 검사하여 삭제해 준다
int count = getCount();
for (int i = 0 ; i < count ; i++)
editText.removeTextChangedListener(getItem(i).mTextWatcher);
}
}
}



main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:gravity="center_vertical">

<ListView android:id="@+id/ItemListView"
android:layout_width="match_parent" android:layout_height="match_parent"/>

</LinearLayout>



list_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:padding="10dp" android:gravity="center_vertical">

<TextView android:id="@+id/NameTextView"
android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"
android:layout_marginRight="5dp"/>

<EditText android:id="@+id/ValueEditText"
android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2"/>

</LinearLayout>



array.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

<string-array name="list_item">
<item>사자</item>
<item>호랑이</item>
<item>토끼</item>
<item>코끼리</item>
<item>강아지</item>
<item>고양이</item>
<item>표범</item>
<item>송아지</item>
<item>돼지</item>
<item>코알라</item>
<item>캥거루</item>
<item>양</item>
<item>염소</item>
<item>늑대</item>
<item>여우</item>
<item>닭</item>
<item>독수리</item>
<item>쥐</item>
<item>뱀</item>
<item>말</item>
<item>곰</item>
<item>두더지</item>
<item>사슴</item>
<item>노루</item>
<item>하마</item>
<item>악어</item>
<item>꿩</item>
<item>비둘기</item>
<item>까마귀</item>
<item>고슴도치</item>
</string-array>

</resources>




clearTextChangedListener() 함수를 주석처리 했을때 (예전 변경 리스너를 지워주지 않았을때)






clearTextChangedListener() 함수를 주석처리 하지 않았을때  (예전 변경 리스너를 지워주었을때)






 

 



수제 앱 장인: 고영진


고영진모바일 1인기업 대표 겸 개발자 

  

     실패만 하고 있어도 꿈을 포기하지 않는 남자 

     제가 직접 경험하고 습득한 지식을 위주로 올릴게요

 


사업자 정보 표시
고영진모바일 | 고영진 | 서울특별시 관악구 낙성대동 서울대연구공원 SK상생혁신센터 | 사업자 등록번호 : 109-11-82076 | TEL : 010-9990-3674 | Mail : gyjmeba@hanmail.net | 통신판매신고번호 : 2010-서울강서-0217호 | 사이버몰의 이용약관 바로가기
TOTAL 69,581 TODAY 10