[WWDC 2014] 직감적으로 디자인 하기

필자는 개발자이다. 하지만 디자인에도 관심이 많은 편이다. 왜냐하면, 많은 사람들이 좋아하는 제품을 만들고 싶기 때문이다. 아무리 뛰어난 기능을 가진다 한들 그 진가는 디자인이라는 언어로 사용자에게 다가갈 때 생긴다고 믿기 때문이다.

샌프란시스코에서 열린 WWDC 2014에서 발표한 ‘Designing Intuitive User Experience’라는 발표를 듣고 남긴 노트를 나누고자 한다.

“Think Big, start small!”

이 말이 발표한 내용 중 가장 기억에 남는 말이다. 직관적인 디자인을 하기 위해서 여러가지 고려사항이 있겠지만, 제품을 만들 때 가장 밑 바닥에 깔려있어야하는 정신이라고 생각된다. 스타트업 기업들을 보면 자신이 가지고 있는 아이디어에 많은 사람들은 흥분에 도가니에 살아간다. 하지만 정작 제품이 나오고 나서 시장의 반응이 생각보다 냉냉할 때면 하늘이 문어지는 느낌이 들것이다. 그것은 너무 많은 것을 하려고 하기 때문이다. 디자인도 맞찬가지이다. 미술을 전공하여 그림을 그리는 사람만 디자이너라고 생각하지 않는다. 디자인은 제품을 만드는 과정에 의사 결정이 하는 사람이라면 모두가 디자이너이다. 그런데 자신이 생각하는 뛰어난 아이디어의 빠져 복잡하고 난잡한 기능들 및 디자인이 첨부 되면서 제품은 점점 고질라가 되어간다. 크게 생각하되 작게 시작하자!

5 Tips to make a design intuitive

1. Focus

Screen Shot 2014-06-04 at 5.32.16 PM

 

펜은 글을 쓰는 도구이다. 펜은 글를 쉽게 쓸 수 있게, 그리고 오래 동안 사용할 수 있게, 디자인되어야 한다. 소프트웨어도 맞찬가지이다. 어떻게 사용될 제품인지 명확하게 정의했다면, 그것에 집중해야 한다. 만약 자신의 소프트웨어에 핵심 가치를 지닌 기능의 이상의 것을 넣기 시작하면 처음에 해결하고자 했던 의도와는 달리 다른 모습으로 편하고 말것이다.

2. Simple

Screen Shot 2014-06-04 at 5.36.53 PM

 

제공하고자 하는 핵심 가치가 있다해도 여러가지 기능 및 디자인에 나타내고자 하는 기능이 가릴 수 있다. 그렇기 때문에 Simplicity는 어떤 제품이나 서비스든 핵심 요소이다.

3. Clear

명확성은 여러가지 디자인 요소에 적용할 수있겠다. 그런데 발표자는 그 중에서 언어에 대해서 적용해 설명해 주었다. 언어 선택을 할 때는 다음과 같은 요소를 고려하자.

  • No Big words: 많은 사람이 아는 단어를 선택하자.
  • Avoid Jargon: 너무 전문적인 단어는 피하자.
  • Be descriptive: 묘사적인 언어를 사용하자.
  •  Be succinct: 간결한 것을 사용하자.
  • Avoid truncation: 약식어는 피하자.
  • make text legible: 잘 보이는 글꼴를 사용하자.

4. Easy to Navigate

소프트웨어 제품에서 사용할 때 쉬운 네비게이션 기능을 제공해야 한다. 다음 세가지 질문을 던져보며 디자인을 고려하자.

  • Where am I?
  • Where else can I go?
  • What will I find when I get there?

Screen Shot 2014-06-04 at 5.47.32 PM

 

아이폰 앱이나 안드로이드 앱에서 자주 볼 수 있는 일명 ‘햄버거’ 메뉴를 가지고 발표자는 설명을 했다. 본 기능은 여러가지 장점을 자기고 있지만 단점이 더 많은 UX라고 생각이 된다. 이 디자인을 처음 사용하는 사용자에게 보여준다면 뒤의 세가지 질문에 답을 하지 못 할 것이다. 그렇기 때문에 좋은 네비게이션 디자인을 가지고 있지 않다고 주장한다. 필자도 실제로 해당 디자인을 적용해서 개발을 해보았으며, 여러 앱을 사용하면서 느낀 점은 ‘어렵다’이다. 그래서 일까? 페이스북 앱은 몇 개월 전에 해당 기능을 일단 TabNavigator로 바꾼 사례도 있다. 아마 UX 연구원의 연구의 결실이 있지 않았나 싶다.

5. Platform Savvy

이 것은 많은 제품들을 사용하면서 사용자에게 습관적으로 박혀있는 디자인에서 벗어나지 말자는 뜻이다. 그렇기 때문에 제품을 공식 발표를 하기 전에 사용자 관찰 및 피드백을 받는 것은 ‘Good to be have’가 아니라 ‘Must to be have’인 것이다. 개발하는 자신이 쉽다고 생각한다고 해서 디자인을 하기 보다는 실제로 제품을 사용하게 될 고객에게서 정말 쉬운지 이해가 가지는지 조사를 해봐야 된다는 주장이다.

마치며…

나는 iOS 개발자이기 때문에 매년 열리는 WWDC를 관심있게 보지만, 기술적인 내용만 있는 것이 아니라 디자인과 UX에 관한 발표도 유심히 즐겨 보는 편이다. 그렇기 때문에 시장에 디자인 좋고 인기 많은 킬러 앱들이 만들어지고 있지 않은가 생각이 된다. 기본적인 컨셉 알아가는 것과 많은 제품을 사용하여 자신의 센스를 늘린다면 개발자도 디자인에 관해 좋은 눈을 가지게 될 것이라고 믿는다.

Dynamic type support

Screen Shot 2014-05-29 at 4.39.24 PM

 

iOS7에서 지원하는 기능 중에 글자 크기를 바꿀수 있는 기능이 있다. OS에서 지원하는 앱 뿐만이 아니라 만약 서드 파티에서도 해당 기능을 구현했다면 동일하게 기능을 사용할 수 있다. 간단하게 어떻게 적용할 수 있는지에 대해서 적어 보았다.

 

1. Register Notification

가장 먼저해야 하는 것은 시스템에서 dispatch하는 notification을 잡기 위해서 옵저버를 등록해야한다.

  1. [[NSNotificationCenter defaultCenter]
  2.     addObserver:self
  3.     selector:@selector(preferredContentSizeChanged:)
  4.     name:UIContentSizeCategoryDidChangeNotification
  5.     object:nil];

 

2. Make a handler method

핸들러 메서드 안에서 UIFont 클래스의 preferredFontForTextStyle: 메서드를 통해서 글꼴 스타일을 적용한다.

  1. - (void)preferredContentSizeChanged:(NSNotification *)notification
  2. {
  3.     [self.tableView reloadData];
  4.  
  5.     //OR
  6.  
  7.     self.mainTextView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
  8. }

 

해외 튜터리얼 참조

[OODesign Principles] ISP (Interface Segregation Principle)

5 Principles of Class Design

  1. SRP (Single Responsibility Principle)
  2. OCP (Open Closed Principle)
  3. LSP (Liskov Substitution Principle)
  4. ISP (Interface SegregationPrinciple)
  5. DIP (Dependency Inversion Principle)

클래스 디자인에 있어 다섯가지 원칙이 있다. ISP에 대한 내용이 기억이 나질 않아 검색 중에 나온 좋은 링크가 있어서 올려보고자 한다.

Link

결론: 뚱뚱한 interface를 구지 구현하지 말고 더 잘게 잘게 인터페이스를 나누어서 구현하도록 하자.

[iOS7 UI Programming - part 1] Animation Transitioning

Animation Transitioning in iOS7

20130313_osxdock_minimize_genie

많은 개발자들은 UI 개발에 있어서 애니메이션이란 자신이 만든 프로덕에 있어서 선물 보장지와 같은 생각을 가지고 있는 것 같다. 중요하지 않지만 있으면 좋을 것 같고, 유행에 예민할 것 같은 주제라고나 할까? 뭐 사실 소프트웨어 개발에 있어서 트랜드에 예민한 디자인 요소라는 것을 부정하지는 않는다. 하지만 Mac OS X에서는 위 이미지와 같은 요술 램프 지니 애니메이션은 10년이 넘은 효과이기에 위 효과만 보더라도 아직까지 쓰이고 있는 것으로 보아 어찌보면 유행이라는 단어와 어울리지 않을지도 모른다는 생각을 해본다.

이번 주제는 iOS7의 Animation Transitioning에 대한 샘플 코드를 올려보고자 한다. iPhone에 있어서 UINavigationController 같은 앱 전체의 굵은 뼈대가 되는 컴포넌트에서 Push라든지 MadalView를 보여주는 기본 애니메이션이 이제 사용자들에게 슬슬 지루감을 주고 있기 때문일까? iOS7에서 해당 기술을 지원하는 코드가 잘 녹아들어있다. UINavigationController나 UIViewController 클래스만 보더라도 Delegate Protocol들을 통해서 이를 완벽하게 ‘레고 블럭’처럼 잘 설계를 해놓았다. UIViewControllerTransitioningDelegate이나 UINavigationControllerDelegate에 정의된 method에서 따로 구현된 클래스 객체만 잘 리턴해도 멋진 자신만의 애니메이션을 구현할 수 있다.

 

Output

 

Source Code

위 동영상의 커스텀 Animation Transitioning은 두가지이다. 첫 번째 ModalView가 나올 때 적용된 효과는 공이 튀기는 것과 같은 효과를 주는 바운드 효과이고 살아질 때 나오는 것은 줄어드는 효과인데, 임의대로 만든 효과이다. 먼저 살펴보고자 하는 클래스는 UITableViewController를 상속 받고있는 MasterViewController이다.

  1. #import "MasterViewController.h"
  2. #import "DetailViewController.h"
  3. #import "AppDelegate.h"
  4. #import "Cat.h"
  5. #import "BouncePresentAnimationController.h"
  6. #import "ShrinkDismissAnimationController.h"
  7.  
  8. #pragma mark – Interface of 'MasterViewController'
  9.  
  10. @interface MasterViewController ()<UIViewControllerTransitioningDelegate>
  11.  
  12. @end
  13.  
  14. #pragma mark – Implementation of 'MasterViewController'
  15.  
  16. @implementation MasterViewController
  17. {
  18.     BouncePresentAnimationController *_bounceAnimationController;
  19.     ShrinkDismissAnimationController *_shrinkDismissAnimationController;
  20. }
  21.  
  22. #pragma mark – Getters & Setters
  23.  
  24. - (NSArray *)cats
  25. {
  26.     return ((AppDelegate *)[[UIApplication sharedApplication] delegate]).cats;
  27. }
  28.  
  29. #pragma mark – Life cycle
  30.  
  31. - (instancetype)initWithCoder:(NSCoder *)aDecoder
  32. {
  33.     if (self = [super initWithCoder:aDecoder])
  34.     {
  35.         _bounceAnimationController = [BouncePresentAnimationController new];
  36.         _shrinkDismissAnimationController = [ShrinkDismissAnimationController new];
  37.     }
  38.  
  39.     return self;
  40. }
  41.  
  42. - (void)viewDidLoad
  43. {}
  44.  
  45. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
  46. {}
  47.  
  48. #pragma mark – Table View
  49.  
  50. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  51. {}
  52.  
  53. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  54. {}
  55.  
  56. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  57. {}
  58.  
  59. #pragma mark – Protocol of 'UIViewControllerTransitioningDelegate'
  60.  
  61. //#1.
  62. - (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
  63. {
  64.     return _bounceAnimationController;
  65. }
  66.  
  67. //#2.
  68. - (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
  69. {
  70.     return _shrinkDismissAnimationController;
  71. }

위 코드는 Class extension에 UIViewControllerTransitioningDelegate를 implement한다고 선언한 후, animationControllerForPresentedController:presentingController:sourceController: 메서드와 animationControllerForDismissedController:메서드를 구현하여, 애니메이션이 구현 되어 있는 클래스 객체를 반환만 해주면 된다.

다음은 _bounceAnimationController 객체의 클래스 소스코드이다.

  1. #pragma mark – Interface of 'BouncePresentAnimationController'
  2. @import Foundation;
  3. @interface BouncePresentAnimationController : NSObject <UIViewControllerAnimatedTransitioning>
  4. @end
  5.  
  6. #pragma mark – Implementation of 'BouncePresentAnimationController'
  7. @implementation BouncePresentAnimationController
  8.  
  9. //0.
  10. - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
  11. {
  12.     return 0.5;
  13. }
  14.  
  15. - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
  16. {
  17.     //1. obtain state from the context
  18.     UIViewController *toViewController = nil;
  19.     toViewController = [transitionContext
  20.                         viewControllerForKey:UITransitionContextToViewControllerKey];
  21.     CGRect finalFrame = [transitionContext finalFrameForViewController:toViewController];
  22.     UIViewController *fromViewController = nil;
  23.     fromViewController = [transitionContext
  24.                           viewControllerForKey:UITransitionContextFromViewControllerKey];
  25.  
  26.     //2. obtain the container view
  27.     UIView *containerView = [transitionContext containerView];
  28.  
  29.     //3. set initial state
  30.     CGRect screenBounds = [[UIScreen mainScreen] bounds];
  31.     toViewController.view.frame = CGRectOffset(finalFrame, 0, screenBounds.size.height);
  32.  
  33.     //4. add the view
  34.     [containerView addSubview:toViewController.view];
  35.  
  36.     //5. animate
  37.     NSTimeInterval duration = [self transitionDuration:transitionContext];
  38.  
  39.     [UIView animateWithDuration:duration
  40.                           delay:0.0
  41.          usingSpringWithDamping:0.6
  42.           initialSpringVelocity:0.0
  43.                         options:UIViewAnimationOptionCurveLinear
  44.                      animations:^{
  45.                          fromViewController.view.alpha = 0.2;
  46.                          toViewController.view.frame = finalFrame;
  47.                      }
  48.                      completion:^(BOOL finished) {
  49.                          //6. Set completed! (Important!)
  50.                          [transitionContext completeTransition:YES];
  51.                          fromViewController.view.alpha = 1.0;
  52.                      }];
  53. }
  54.  
  55. @end

 

BouncePresentAnimationController 코드 분석

0. transitionDuration: 메서드는 이름을 보면 알 수 있듯이 transition 타임을 결정 해준다.
1. 새로들어 올 ViewController과 사라지게 될 ViewController를 assign 해준다.
2. transitionContext에는 containerView라는 view가 있다. Transitioning중에 보여질 View 객체들은 ContainerView 안에 있어야 한다.
3. toViewController의 Frame을 세팅한다.
4. ContainerView에 toViewController를 추가한다.
5. UIView의 animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:를 통해 바운스 애니메이션을 구현한다.

다음은 _shrinkDismissAnimationController의 클래스 코드를 살펴보자.

  1. #pragma mark – Interface of 'ShrinkDismissAnimationController'
  2.  
  3. @import Foundation;
  4. @interface ShrinkDismissAnimationController : NSObject<UIViewControllerAnimatedTransitioning>
  5. @end
  6.  
  7. #pragma mark – Implementation of 'ShrinkDismissAnimationController'
  8.  
  9. @implementation ShrinkDismissAnimationController
  10.  
  11. - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
  12. {
  13.     return 0.5;
  14. }
  15.  
  16. - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
  17. {
  18.     //1.
  19.     UIViewController *toViewController = nil;
  20.     toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
  21.     UIViewController *fromViewController = nil;
  22.     fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  23.     CGRect finalFrame = [transitionContext finalFrameForViewController:toViewController];
  24.     UIView *containerView = [transitionContext containerView];
  25.  
  26.     toViewController.view.frame = finalFrame;
  27.     toViewController.view.alpha = 0.5;
  28.  
  29.     //2
  30.     [containerView addSubview:toViewController.view];
  31.     [containerView sendSubviewToBack:toViewController.view];
  32.  
  33.     //3. Actual animation
  34.     CGRect fromViewFrame = fromViewController.view.frame;
  35.     CGRect screenBounds = [[UIScreen mainScreen] bounds];
  36.     CGRect shrunkenFrame = CGRectInset(fromViewFrame, fromViewFrame.size.width/4, fromViewFrame.size.height/4);
  37.     CGRect fromFinalFrame = CGRectOffset(shrunkenFrame, 0, screenBounds.size.height);
  38.     NSTimeInterval duration = [self transitionDuration:transitionContext];
  39.  
  40.     //4. animate
  41.     [UIView
  42.      animateKeyframesWithDuration:duration
  43.      delay:0.0
  44.      options:UIViewKeyframeAnimationOptionCalculationModeCubic
  45.      animations:^
  46.     {
  47.  
  48.         [UIView addKeyframeWithRelativeStartTime:0.0
  49.                                 relativeDuration:0.5
  50.                                       animations:^{
  51.                                           fromViewController.view.transform = CGAffineTransformMakeScale(0.5, 0.5);
  52.                                           toViewController.view.alpha = 0.5;
  53.                                       }];
  54.  
  55.         [UIView addKeyframeWithRelativeStartTime:0.5
  56.                                 relativeDuration:0.5
  57.                                       animations:^{
  58.                                           fromViewController.view.frame = fromFinalFrame;
  59.                                           toViewController.view.alpha = 1.0;
  60.                                       }];
  61.     }
  62.      completion:^(BOOL finished)
  63.     {
  64.         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
  65.     }];
  66. }
  67.  
  68. @end

 

ShrinkDismissAnimationController 코드 분석

1. 새로들어 올 ViewController과 사라지게 될 ViewController를 assign 해준다.
2. transitionContext에는 containerView라는 view가 있다. Transitioning중에 보여질 View 객체들은 ContainerView 안에 있어야 한다.
3. 애니메이션에 필요한 값
4. 실제 애니메이션 구현은 UIView의 animateKeyframesWithDuration:delay:options:options:animations:completion: 안에 또 다른 애니메이션 메서드를 구현함을 통해 원하는 애니메이션을 구현한다.

 

마치며..

UI 프로그래밍이 시대의 유행이든 아니든 간에 SDK의 설계를 보며 느끼는 점이 많았던 리서치 같다. 더 공부하고 더 열심히 해야겠다는 생각을 해본다.

[펌글] 3D 기본 수학

해당 포스트는 iOS의 Core Animation을 공부하다가 본 좋은 블로그라 생각하여서, 링크를 공유해본다.

Basic 3D Math: Matrices by Egon Rath’s Notes

Basic 3D Math: Matrices

Matrices are rectangular mathematical objects which have a number of rows and columns – think of it as a two-dimensional array. Take a look at the following:

A = delim{[}{matrix{3}{4}{1 2 3 4 5 6 7 8 9 10 11 12}}{]}

Above is a 3×4 Matrix (because it has 3 rows and 4 columns) – speak: 3 by 4 Matrix. Each element of the Matrix can be indexed by using the following notation:

A_ij where i is the row and j the column: A_32 = 10 because the element at the 3rd rows and 2nd column has the value 10.

Matrices are used in CG to represent a various number of different things like transformations and orientations.

Part 1: Multiplying Matrices (Matrix Product)

Matrix multiplication is not commutative, which means that the order of operation is relevant (as opposed to the multiplication of real numbers). In order to be able to multiply two matrices they must satisfy the rule that column of the first must match the rows of the second. Take a look at the following graphic which works as a simple reminder for the rules of matrix multiplication:

But what’s the purpose of multiplying matrices? In 3D Programming you do Transforming a vertex with such a operation. Stop – how can i multiply a vector (which stores a vertex position) with a matrix? Vectors can be thought as Vectors with 1 Row and 4 Columns – and because multiplying a 1×4 Matrix with a 4×4 Matrix is a valid operation this works.

In the first step, lets examine how to actually do the multiplication – formally this looks like:

(AB)_ij = sum{k=1}{p}{A_ik B_kj}

i = row index
j = column index
p = number of columns of the first matrix (or number of rows of the second one)

Because a example often says more than thousand words, take a look at the following:

We have two matrices: A = delim{[}{matrix{2}{3}{1 2 3 4 5 6}}{]} and B = delim{[}{matrix{3}{2}{10 11 12 13 14 15}}{]}. Multiplication of those two is possible because the number of columns of A matches the number of rows of B – the result is a 2×2 Matrix.

Let’s perform the Calculation step by step.

Step 1: Write down the calculation of every index for the resulting matrix

C_{i=1,j=1}=1*10 + 2*12 + 3*14 C_{i=1,j=2}=1*11 + 2*13 + 3*15
C_{i=2,j=1}=4*10 + 5*12 + 6*14 C_{i=2,j=2}=4*11 + 5*13 + 6*15

Step 2: Do the calculations

10 + 24 + 42 11 + 26 + 45
40 + 60 + 84 44 + 65 + 90

results in:

C = delim{[}{matrix{2}{2}{76 82 184 199}}{]}

Part 2: Creating Matrices for Transformations

As mentioned above at the beginning of this document, matrices can be used to transform vectors. Its obvious that you need corresponding matrices which represent the transformation, so they are presented below – please note that they are for OpenGL only (The “why” is discussed at the end)

Identity Matrix:

I = delim{[}{matrix{4}{4}{1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1}}{]}

The Identity matrix can be seen as the “reset” state matrix. If you multiply a vector with the Identity matrix you get the original vector. You initialise new matrices to the identity.

Translation Matrix:

T = delim{[}{matrix{4}{4}{1 0 0 X 0 1 0 Y 0 0 1 Z 0 0 0 1}}{]}

X, Y and Z represent the amount you want to translate the vector on the axis.

Scaling Matrix:

S = delim{[}{matrix{4}{4}{X 0 0 0 0 Y 0 0 0 0 Z 0 0 0 0 1}}{]}

X,Y and Z represent the amount of scaling on the corresponding axis.

Rotation Matrix:

There are four rotation matrices – one about each Axis (X,Y and Z) and one for rotating about an arbitrary axe. alpha is the roation in radians.

Rot_X = delim{[}{matrix{4}{4}{1 0 0 0 0 {cos(alpha)} {-sin(alpha)} 0 0 {sin(alpha)} {cos(alpha)} 0 0 0 0 1}}{]}  Rot_Y = delim{[}{matrix{4}{4}{{cos(alpha)} 0 {sin(alpha)} 0 0 1 0 0 {-sin(alpha)} 0 {cos(alpha)} 0 0 0 0 1}}{]}  Rot_Z = delim{[}{matrix{4}{4}{{cos(alpha)} {-sin(alpha)} 0 0 {sin(alpha)} {cos(alpha)} 0 0 0 0 1 0 0 0 0 1}}{]}

The matrix used to rotate about an arbitrary axis is a little bit more complicated.

Assumptations:

c = cos(alpha) s = sin(alpha) t = 1-cos(alpha)

Then the matrix looks like:

Rot = delim{[}{matrix{4}{4}{{tX^2+c} {tXY + sZ} {tXZ - sY} 0 {txY - sZ} {tY^2+c} {tYZ + sX} 0 {tXY + sY} {tYZ - sX} {tZ^2+c} 0 0 0 0 1}}{]}

The Vector (x,y,z) which represents the rotation axis must be normalized

Most of the time you don’t need to create those matrices by ourself because either the math library you are using provides them or you write your own.

Part 3: Order of operation and combining transformations

The order of multiplying multiple transformation matrices is cruical. In OpenGL you get the final transformation by reading your statement from left to right. Example:

vec{U} = R * T * vec{V}

R is a rotation, T a translation matrix. The Vector V is first Translated and then rotated. Take a look at the pictures below to see the difference visually:

In this Picture, the vertices of the Object are first multiplied by a rotation matrix (about Z) – which rotates the local object coordinates. After that a translation matrix (move along X)  is applied, so the object moves along the rotated X axis. The order of the multiplications are:

vec{U} = M_{trans} * M_{rotation} * vec{V}

As mentioned before, we read from right to left (Take Vector V, then Rotate, then Translate)

This time we first multiply with a translation matrix (translate along X) and then apply a rotation matrix:

vec{U} = M_{rotation} * M_{translation} * vec{V}

Which means: Take Vector V, then translate, then rotate.

Please note that the order only applies to OpenGL. In DirectX it’s from left to right – also the matrices are a little bit different in how they look like.

Part 4: Going ahead

Most OpenGL based 3D Libraries and Toolkits provide easy ways of creating the matrices mentioned above (i REALLY like Richard Wright’s Math3D Library which he uses in his Book OpenGL Superbible).

If you want to dig deeper into the mathematics behind 3D Graphics, i highly recommend you the following two books:

[Facebook-Tweaks] 간단하게 살펴보기.

Tweaks Framework by FB

이틀 전 (May.24.2014) 페이스북에서 iOS 프래임워크 하나를 Github에 올리면서 공개를 했다. 해당 프래임워크의 주 기능은 간단하다. 완성된 output의 사소한 속성들을 변경하고 싶을 때 개발자의 손에 걸치지 않고 Runtime으로 수정 가능한 기능을 제공해 준다는 것이다. (아마 무슨 뜻인지 이해가 가질 않을 것으로 생각된다.)

 

“Why do I need it?”

예를 들어 개발자가 애니메이션의 Duration을 정해서 컴파일을 할 것이다. 그런데 모션 그래픽 디자이너라든지 기획자가 보기에 너무 길다던가 맘에 안들 수 있다. 그런데 일일이 개발자 옆에 앉아서 테스트 해보거나 혹은 Bug Tracking System 같은 경로를 통해서 이와 같은 다양한 주문을 시도할 것이다. 하지만 이런 과정은 프로젝트 전체를 보았을 때 상당히 값이 비싼 비용이다. 개발자와 디자이너와 신경전을 한 두번 해본 개발자라면 이 프래임워크의 큰 가치를 깨달을 것이다.

 

“What is ‘Tweaks’?”

Tweaks는 이런 문제점을 해결해주는 좋은 대안이 될 수 있다. 프래임워크를 사용하여 디자이너 혹은 기획자가 직접 UI의 속성을 바꿔가면서 테스트해볼 수 있는 대안이 생겼기 때문이다.

from github

위 gif 이미지를 잠시 보자. ‘Tweaks’라는 UILabel 객체가 화면에 있다. 그런데 어떤 이유에서든지 디자이너가 색상이 맘에 들지 않아서 이를 변경하고자 한다. 일반적으로 디자이너는 개발자에게 이메일을 보내거나 가끔은 자리에 찾아와 색상 변경을 요구하며 이것 저것을 테스트 해보길 요구할 것이다. 이럴 때에 개발자에게 있어서는 얼마나 방해가 되는 일인지 모르며 이런 작은 방해들이 점점 모여서 프로젝트의 비용을 점점 비싸게 만드는 것이다. 그런데 (개발하기 나름이겠지만… ) 본 프래임워크를 적용해서 폰을 흔들면 UI 속성을 바꿀수 있는 TableView가 나온다. 그럼 해당 UI의 속성을 바꿈으로 개발자의 손을 빌리지 않고 이것 저것 tweaking을 할 수 있는 길이 생긴 것이다.

 

“How to use?”

github 페이지에 설치 방법 부터 사용 방법이 잘 나와 있다. 가장 간단히 설치할 수 있는 방법은 CocoaPod를 사용하는 것이다. 소스코드의 순서를  간단하게 보면 다음과 같을 것이다.

  1. 프래임워크 Import 함.
  2. FBTweakViewController를 화면에 뿌릴수 있는 장치를 만듬.
  3. UI 객체 인스턴스의 속성에 바인딩 혹은, ‘FBTweaksValue’를 통해  설정 값 할당 함.

 

프래임워크 Import 하기

  1. #import <FBTweak/FBTweak.h> //FBTweak 기본 헤더파일
  2. #import <FBTweak/FBTweakShakeWindow.h> //폰을 흔들어 FBTweaksViewController를 열고 싶을 때 사용하기
  3. #import <FBTweak/FBTweakInline.h> //FBTweakInline 객체 사용할 때 필요
  4. #import <FBTweak/FBTweakViewController.h> //FBTweakViewController를 presentViewController:animated:completion:을 통해 열때 필요

 

FBTweakViewController를 화면에 뿌릴 장치

변경 가능한 UI 속성을 제어할 UITableView를 화면에 나타내주기 위해서 크게 두 가지 방법이 있을 것이다. 첫 번째는 폰을 흔들어서 나타나게 하는 방법과 임시의 버튼을 만들어서 presentViewController:animated:completion: 메서드를 사용하여 FBTweakViewController 객체를 화면서 나타나게 하는 방법이다. 둘 중에 편한 방법으로 사용하면 될 것이라고 생각 되어진다.

 

UI 속성 바인딩 혹은 설정 값을 Tweaks 함수를 통해 할당

이제 자신이 디자이너들이나 타인들에게 Runtime 때에 변경 가능한 UI 속성을 주고자 하는 코드에 FBTweakValue 함수를 통해서 값을 할당 하든지, FBTweakBind 함수를 통해서 값을 바인딩하면 된다.

  1. //#. Binding 예
  2. UILabel *_label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, _window.bounds.size.width, _window.bounds.size.height * 0.75)];
  3. _label.textAlignment = NSTextAlignmentCenter;
  4. _label.numberOfLines = 0;
  5. _label.userInteractionEnabled = YES;
  6. _label.backgroundColor = [UIColor clearColor];
  7. _label.textColor = [UIColor blackColor];
  8.  
  9. //1-a
  10. _label.font = [UIFont systemFontOfSize:FBTweakValue(@"Content", @"Text", @"Size", 60.0)];
  11.  
  12. //1-b. UILabel 객체의 텍스트 속성을 동적인 값으로 바인딩
  13. FBTweakBind(_label, text, @"Content", @"Text", @"String", @"Tweaks");
  14.  
  15. //1-c. UILabel 객체의 알파 속성을 동적인 값으로 바인딩
  16. FBTweakBind(_label, alpha, @"Content", @"Text", @"Alpha", 0.5, 0.0, 1.0);

‘FBTweakBind’ 함수 사용법은 다음과 같다.

  1. FBTweakBind(object_, property_, category_, collection_, name_, …)

  • object: UI 오브젝트 인스턴스
  • property: 객체 속성명
  • category: TableView에 나올 TableViewCell 명
  • collection: grouped tableView에 표시 될 그룹 명
  • name: 해상 TableViewCell에 표시 될 title 명
  • …: 기본 설정 값

‘FBTweakValue’ 함수 사용법은 다음과 같다

  1. FBTweakValue(category_, collection_, name_, …)

  • category: TableView에 나올 TableViewCell 명
  • collection: grouped tableView에 표시 될 그룹 명
  • name: 해상 TableViewCell에 표시 될 title 명
  • …: 기본 설정 값

그리고 나서 뒤에 오는 값들은 기본 설정 값이다. 이 기본 설정에 따라 TableViewCell에 나타날 설정 가능하도록 해주는 UI가 결정이 된다. 1-b에는 NSString인 ‘Tweaks’로 설정되어 있고, 1-c에서는 세개의 값이 있다. 첫 번째 값인 0.5는 디폴트 값이며, 두번째는 설정 가능한 가장 낮은 값, 세번째 값은 설정 가능한 가장 큰 값이다. 이렇게 설정을 하면 UIStepper를 통해 설정이 가능하게 해준다. 만약에 이 값에 YES와 같은 Bool 값이 들어오면 Toggle 가능한 UISwitch가 들어 올 것이다.

Where to go next?

github에 올라온 소스를 자신의 컴퓨터에 클론하여 예제 앱을 사용해 보면 자신의 프로젝트에 어떻게 사용할지 쉽게 알게 될 것이다. 위에 설명한 것들은 정말 기본적이고 쉬운 것들이고, 더 advance한 사용은 github에 나와있는 설명과 stackoverflow를 통해 알아보기 바란다. 혹자도 기존 프로젝트에 적용해보고 추가할 내용이 생기면 추후에 본 블로그를 더 업데이트 하겠다.

개발자을 위한 iOS7 디자인 Tips

Design?

개발자는 개발만 한다?! 비쥬얼 베이직의 아버지 ‘앨랜 쿠퍼‘가 ‘정신병원에서 뛰쳐나온 디자인‘이라는 책에서 주장했던 내용이다.

하지만 iOS 개발자라면 크게 인정하지 못할 이야기일지도 모른다. 많은 개발자가 cool한 애플의 UI/UX에 매료되어서 개발을 시작한 사람들이 많을 것이기 때문이다. 하지만 나는 전적으로 그의 주장에 공감하는 편이었다. 하지만 요즘 드는 생각은 개발자도 기본적인 디자인 요소에 대해서 알아야한다는 것이 내 생각이다. 아는 것과 모르는 것은 분명 고민을 한 결과물과 고민 없이 만든 결과물과 나오는 효과나 반응이 분명 큰 차이가 있다고 생각하기 때문이다.

다음 가이드는 내가 즐겨찾는 블로그에서 본 내용을 정리해 보았다. 조금이나마 도움이 되길 바란다.

출처 Ray Wenderlich

기본에 집중하라

  • Contrast(대조/대비)

    • 하이라이트: 화면에서 중요한 부분은 강조되어야 한다.
    • Eye candy
    • 상태: 각각의 UI가 터치 가능한지 알려줘야 한다.
    • 가독성: 글씨가 잘보이는 확인하라.

 

 

Screen Shot 2014-03-21 at 12.16.43 PM

Screen Shot 2014-03-21 at 12.16.55 PM

  • Repetition(반복)

Screen Shot 2014-03-21 at 12.19.54 PM Screen Shot 2014-03-21 at 12.20.04 PM

  • Alignment(정렬)

alignment

  • Proximity(근접)

Screen Shot 2014-03-21 at 12.24.16 PM

Screen Shot 2014-03-21 at 12.24.28 PM

  • Typography(글꼴)

    • 최대 3가지 다른 글꼴만 써라.
    • 중앙 정렬은 꼭 필요할 때만 사용하라
    • 글꼴 선택은 최대한 심플하게하라.
    • 글자 리사이징에 대한 대비를 하자.
    • 큰 블럭 글자는 ‘serif’ 글꼴을 쓴다.
    • 적은 양의 글자는 ‘sans serif’ 글꼴을 쓴다.
    • 한 화면에서 대비 되는 글꼴을 써라.(Use contrasting font-families on the same screen.)
    • 한 화면에서 글꼴의 특성을 다양하게 쓰고 싶으면, 한 글꼴로 해라. (글꼴 특성이란, 이텔리체, 볼드체, 사이즈, 색깔등을 말한다.)

Screen Shot 2014-03-21 at 12.25.38 PM

Screen Shot 2014-03-21 at 12.25.47 PM

내용에 집중하라

  • 불필요한 디테일은 삭제하라

  • 크롬을 강조하지 마라 (Navigation bar, tool bar, and etc.)

Screen Shot 2014-03-21 at 12.27.32 PM

  • 심플한 배경

  • 필요하면 크롬을 감춰라

Screen Shot 2014-03-21 at 12.28.30 PM

  • UI 요소를 간단히하라

  • 보더 없는 버튼

  • 색깔을 사용하여 터치 가능한지 않한지를 구분하라

  • 현실적인 3D 디자인을 줄여라

Screen Shot 2014-03-21 at 2.12.02 PM

 

컨텐츠에 집중하기

  • 전체 화면을 써라

  • 컨텐츠도 디자인의 일부이다

Screen Shot 2014-03-21 at 2.14.20 PM

  • 시각적으로 재미있게 만들라.

Screen Shot 2014-03-21 at 2.17.03 PM

 

 

마치며…

이 글을 통해 그동안 느낌으로만 디자인 감각에 충실했던 개발자들에게 기본적인 개념을 심어주었으면 좋겠다. 마지막으로 학부 시절 때 들었던 미술 교양 강사님의 말이 생각을 전하며 마무리를 하고 싶어졌다.

‘디자인 감각은 선천적인 것이 아니다. 절대적으로 후천적으로 개발되는 것이다. 그럼 어떻게 해야 감각이 생길까? 많이 봐라!’

옷을 잘 입는 사람이나 디자인을 잘한다고 하는 사람들이 입을 모아서 하는 말이다. 앱을 많이 써보고 어떤 부분에서 이상한지, 어떤 부분에서 느낌이 좋은지 그냥 지나치지 말고 머리로 한번 더 생각을 해보자. 개발자들의 특성상 앱이나 서비스를 보게 되면 어떻게 작동할 것 같은지 혹은 어떤 기술을 썼는지에 관심을 더 갖게 되어 있다. 하지만 우리도 이젠 디자인이라는 포장 및에 모습만 보려하지말고 어떤 포장을 썼는지에도 관심을 가지고 더욱 User-Friendly한 개발자가 되어야겠다.

[iOS7 DynamicAnimator] 샘플 코드

Screen Shot 2014-03-21 at 11.20.01 AM

iOS7에는 비-게임을 위한 물리엔진이 장착되어 있다. UIDynamicAnimator를 사용하여 간단한 실험을 해볼 수 있다. 코드에 대한 설명은 코드 아래 설명을 참조하라.

  1. #pragma mark – Interface of 'ViewController'
  2.  
  3. @interface ViewController ()<UICollisionBehaviorDelegate>
  4. @end
  5.  
  6. #pragma mark – Implementation of 'ViewController'
  7.  
  8. @implementation ViewController
  9. {
  10.     //#0
  11.     UIDynamicAnimator *_animator;
  12.     UIGravityBehavior *_gravity;
  13. }
  14.  
  15. #pragma mark – Life cycle
  16.  
  17. - (void)viewDidLoad
  18. {
  19.     [super viewDidLoad];
  20.  
  21.     //#1
  22.     UIView *red = [self boostupRedRectangle];
  23.     UICollisionBehavior * collision = [self boostupAnimationAndBehaviorApplyToView:red];
  24.  
  25.     //#2
  26.     UIView *yellow = [self boostupYellowRectangle];
  27.     [self addBarrierOnCollision:collision
  28.                 withBarrierView:yellow
  29.           andBoundaryIdentifier:@"yellow"];
  30.  
  31.     //#3
  32.     UIView *blue = [self boostupBlueRectangle];
  33.     [self addBarrierOnCollision:collision
  34.                 withBarrierView:blue
  35.           andBoundaryIdentifier:@"blue"];
  36. }
  37.  
  38. - (void)didReceiveMemoryWarning
  39. {
  40.     [super didReceiveMemoryWarning];
  41.     // Dispose of any resources that can be recreated.
  42. }
  43.  
  44. #pragma mark – Protocol of 'UICollisionBehaviorDelegate'
  45.  
  46. - (void)collisionBehavior:(UICollisionBehavior*)behavior
  47.       beganContactForItem:(id <UIDynamicItem>)item
  48.    withBoundaryIdentifier:(id <NSCopying>)identifier
  49.                   atPoint:(CGPoint)p
  50. {
  51.  
  52. }
  53.  
  54. #pragma mark – Boostup UIs
  55.  
  56. - (UIView *)boostupRedRectangle
  57. {
  58.     CGRect redFrame = CGRectMake(50, 50, 100, 100);
  59.     UIView *redRectangle = [[UIView alloc] initWithFrame:redFrame];
  60.     [redRectangle setBackgroundColor:[UIColor redColor]];
  61.     [self.view addSubview:redRectangle];
  62.  
  63.     return redRectangle;
  64. }
  65.  
  66. - (UIView *)boostupYellowRectangle
  67. {
  68.     CGRect yellowFrame = CGRectMake(0, 300, 90, 20);
  69.     UIView *yelloRectangle = [[UIView alloc] initWithFrame:yellowFrame];
  70.     [yelloRectangle setBackgroundColor:[UIColor yellowColor]];
  71.     [self.view addSubview:yelloRectangle];
  72.  
  73.     return yelloRectangle;
  74. }
  75.  
  76. - (UIView *)boostupBlueRectangle
  77. {
  78.     CGRect blueFrame = CGRectMake(180, 380, 140, 20);
  79.     UIView *blueRectangle = [[UIView alloc] initWithFrame:blueFrame];
  80.     [blueRectangle setBackgroundColor:[UIColor blueColor]];
  81.     [self.view addSubview:blueRectangle];
  82.  
  83.     return blueRectangle;
  84. }
  85.  
  86. - (UICollisionBehavior *)boostupAnimationAndBehaviorApplyToView:(UIView *)view
  87. {
  88.     //#2-1
  89.     _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
  90.     _gravity = [[UIGravityBehavior alloc] init];
  91.     [_gravity addItem:view];
  92.     [_animator addBehavior:_gravity];
  93.     _gravity.magnitude = 4.0f;
  94.  
  95.     //#2-2
  96.     UICollisionBehavior *collision = [[UICollisionBehavior alloc]
  97.                                       initWithItems:@[view]];
  98.     collision.collisionDelegate = self;
  99.     collision.translatesReferenceBoundsIntoBoundary = YES;
  100.     [_animator addBehavior:collision];
  101.  
  102.     return collision;
  103. }
  104.  
  105. - (void)addBarrierOnCollision:(UICollisionBehavior *)collision
  106.               withBarrierView:(UIView *)barrier
  107.         andBoundaryIdentifier:(NSString *)identifier
  108. {
  109.     CGPoint startPoint = barrier.frame.origin;
  110.     CGPoint endPoint = CGPointMake(barrier.frame.origin.x + barrier.frame.size.width, barrier.frame.origin.y);
  111.     [collision addBoundaryWithIdentifier:identifier
  112.                                fromPoint:startPoint
  113.                                  toPoint:endPoint];
  114. }
  115.  
  116. @end

코드 설명

  1. ‘boostupAnimationAndBehaviorApplyToView:’ 메서드에서 보면 _animator가 애니메이션을 적용할 뷰를 넘기며 생성하는 것을 볼 수있다. 그리고 난 다음 UIGravityBehavior 클래스의 인스턴스를 애니메이터에 추가하는 것으로 해당 뷰에 중력 효과를 적용할 수 있다. 그리고 UICollisionBehavior 객체를 통하여서 빨강 네모를 화면안에 가둬넣을 수 있다.
  2. addBarrierOnCollision:WithBarrierView:AndBoundaryIdentifer: 메서드를 통하여서 UICollisionBehavior에 보이지 않는 장벽을 만들 수 있다. 실제로 화면에서 보이는 장애물은 그저 자리만 지키고 있을 뿐이며 UICollisionBehavior의 객체에 보이지 않는 장해물을 세팅하는 메서드이다.

[iOS7 Motion-Effect] UIInterpolatingMotionEffect 샘플 코드

UIInterpolatingMotionEffect

출처: google 이미지 검색 결과

iOS7을 보면 잠금 화면이라든지 바탕화면이 시각에 따라 움직이는 효과를 볼 수 있을 것이다. 애플에서 쉽게 라이브러리로 제공을 하고 있다.

  1. #import "ViewController.h"
  2.  
  3. @interface ViewController ()
  4.  
  5. @end
  6.  
  7. @implementation ViewController
  8.  
  9. - (void)viewDidLoad
  10. {
  11.     [super viewDidLoad];
  12.  
  13.     //1. Lower bg
  14.     UIImage *lowerImg = [UIImage imageNamed:@"bethel_in_carseat.jpg"];
  15.     UIImageView *lowerbg = [[UIImageView alloc] initWithImage:lowerImg];
  16.     lowerbg.frame = CGRectInset(self.view.frame, -50.0f, -50.0f);
  17.  
  18.     [self.view addSubview:lowerbg];
  19.     [self addMotionEffectToView:lowerbg magnitude:50.0f];
  20.  
  21.     //2. mid bg
  22.     UIImage *midImg = [UIImage imageNamed:@"Background-MidLayer.png"];
  23.     UIImageView *midbg = [[UIImageView alloc] initWithImage:midImg];
  24.     [self.view addSubview:midbg];
  25.  
  26.     //3. header bg
  27.     UIImage *headerImg = [UIImage imageNamed:@"Sarnie.png"];
  28.     UIImageView *headerBG = [[UIImageView alloc] initWithImage:headerImg];
  29.     headerBG.center = CGPointMake(220, 190);
  30.     [self.view addSubview:headerBG];
  31.     [self addMotionEffectToView:headerBG magnitude:-20.0f];
  32. }
  33.  
  34. - (void)didReceiveMemoryWarning
  35. {
  36.     [super didReceiveMemoryWarning];
  37. }
  38.  
  39. #pragma mark – Methods
  40.  
  41. - (void)addMotionEffectToView:(UIView *)view magnitude:(CGFloat)magnitude
  42. {
  43.     UIInterpolatingMotionEffect *horMoffec = nil;
  44.     horMoffec = [[UIInterpolatingMotionEffect alloc]
  45.                  initWithKeyPath:@"center.x"
  46.                  type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
  47.     horMoffec.maximumRelativeValue = @(magnitude);
  48.     horMoffec.minimumRelativeValue = @(-magnitude);
  49.  
  50.     UIInterpolatingMotionEffect *verMoffec = nil;
  51.     verMoffec = [[UIInterpolatingMotionEffect alloc]
  52.                  initWithKeyPath:@"center.y"
  53.                  type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
  54.     verMoffec.maximumRelativeValue = @(magnitude);
  55.     verMoffec.minimumRelativeValue = @(-magnitude);
  56.  
  57.     UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init];
  58.     group.motionEffects = @[verMoffec, horMoffec];
  59.  
  60.     [view addMotionEffect:group];
  61. }

[Build Setting::Other Linker Flags] -ObjC와 -all_load의 차이점

Other Linker Flag‘는 XCode 프로젝트에서 서드 파티 라이브러리를 사용할 때 흔히 세팅하는 build setting이다. 해당 플래그는 컴파일 타임 때에 서드 파티의 링크하는 과정에 대한 설정인데, 일반적으로 가장 많이 사용되는 Flag는 ‘-ObjC’와 ‘-all_load’ 이다. 언듯 보면 똑같은 역할을 하는 플래그 처럼 보이지만 다음과 같은 차이가 있다고 한다.

-Objc

This flag causes the linker to load every object file in the library that defines an Objective-C class or category.

해당 값은 링커가 Objective-C 클래스나 카테고리로 정의된 라이브러리에 속한 객체 파일들을 모두 적재하게 해주는 플래그이다.

-all_load

For 64-bit and iPhone OS applications, there is a linker bug that prevents -ObjC from loading objects files from static libraries that contain only categories and no classes. The workaround is to use the -all_load or -force_load flags. -all_load forces the linker to load all object files from every archive it sees, even those without Objective-C code

64 비트 프로젝트나 아이폰 OS 응용 프로그램에서 클래스가 없고 카테고리만 있는 Static 라이브러리의 객체를 -ObjC만 사용하여 적재하려고 할 때 정상 작동을 하지 않는 버그가 있다. 그 버그를 위해 대처 안으로 사용할 수 있는 것이 ‘-all_load‘나 ‘-force_load‘  플래그이다. ‘-all_load‘ 플래그는 모든 객체 파일을 링커로 부터 적재할 수 있도록 하는 플래그이다.

Reference Building Objective-C static libraries with categories

from: http://stackoverflow.com/questions/21844824/difference-between-all-load-and-objc