태안에 위치한 아일랜드 리솜을 다녀왔다. 오션 빌라스 G70에서 1박 2일을 보내고 왔는데 전용면적이 150 제곱미터라 그런지 크고 좋았다.
로비, 체크인
로비는 엄청 크지는 않았지만 리모델링한지 얼마 되지 않아서 깔끔했다.5인 이상 집합 금지 안내문객실안에 VR 등을 할 수 있다는 안내가 있다. 나는 사용하지 않았지만 네틀릭스는 로그인 되어있어서 잘 봤다. (누군가 로그인해놓고 그냥 간듯..)
웰컴 드링크
호텔 체크인을 하면 객실 당 2잔 웰컴 드링크를 제공한다. 로비 바로 옆에 있는 카페에서 받을 수 있는 데 음료는 차 메뉴로 고정되어있다. (커피나 이런것도 선택 가능하면 좋을 것 같다)
오션 빌라스
빌라동 전용 주차장이 있어서 편리했다. 주차장과 객실과의 거리가 엄청 가까움.빌라는 1, 2층으로 나뉘어져 있다. 2층이 G70이고 아랫 층이 G50인 것 같았다.
내부 구조
내부 구조는 크게 방 3개, 화장실 3개, 거실, 부엌으로 각각의 공간이 분리되어 있었고 테라스가 생각보다 컸다. 테라스에서는 바다가 내려다 보인다.
화장실 3개 중 욕조는 큰 방에 위치한 화장실에만 있다.여기는 온돌 방인데 사실 거의 쓸 일이 없었다. 그냥 짐 넣는 방으로 사용함.부엌이 따로 분리되어 있어서 좋았다. 한 가지 불편했던 건 콘센트가 바닥에만 있어서 뭔가 전기를 연결하려면 밥통 같은 것 등등 다 바닥에 내려놓고 사용해야 된다.티비도 최신식이라 좋았고 거실이 넓어서 쾌적햇다. 밖에 테라스로 바로 나갈 수 있고 바다가 보이는 전망대로 시원했다.
부대시설
겨울에도 야외 온천을 운영하고 있고 약간 인피니티 풀처럼 바다를 바라보며 사진을 찍을 수 있게 되어있다. 1층에는 투썸 플레이스가 있고 2층에는 돈스파이크가 운영하는 로우앤슬로우 체인점이 있다. (내가 간 화요일은 운영하지 않는 날이라서 맛보지 못했다)카페에서 바라본 안면도 바닷가 풍경. 다 좋았는데 현수막 광고가 붙어 있어서 멋진 경관 촬영에 방해가 조금 되었다 ㅜ (현수막을 피해서 찍은 사진)일몰이 멋진 안면도
애플티비와 애플워치를 이용해서 피트니스를 할 수 있는 서비스가 출시되었다. 이름은 애플 피트니스 플러스. 아쉽게도 한국에서는 서비스를 하지 않는다.
일단 한국에서 이 서비스를 이용하려면 미국계정이 있어야 되는데 https://appleid.apple.com 에 들어가서 지역을 미국으로 선택하고 계정을 하나 만들면 이용할 수 있다. (가입 시 핸드폰 번호라든가 카드번호 등은 한국에서 사용하는 걸로 등록하면 된다.)
피트니스 플러스 가입하기
Apple TV를 미국계정으로 로그인하면 피트니스 앱이 보인다.정말 다양한 종류의 운동들이 이미 수십개 올라와있다
운동 시작하기를 누르니 피트니스 플러스를 가입하라고 나온다. 아이폰도 미국계정으로 로그인을 하고 피트니스 앱을 열면 한국계정에서는 없었던 피트니스 플러스 메뉴가 보인다.
1달간 무료 체험 선택!1달은 무료고 그 다음부터는 월 9.99불이다. 일단 가입을 진행하자
애플 워치와 연동하기
이 부분은 정말 할말이 많다. 연동하는데 엄청 애를 먹었다. 인터넷을 찾아보니 나랑 비슷한 문제를 겪는 사람들이 많았는데 결론은 애플 워치 재부팅, 아이폰 재부팅 후 다시 연결 시도 등으로 해결했다.
애플워치가 연결되면 PIN 코드를 입력하라고 나온다. 여기까지 왔으면 연결 거의 성공이다.제발 연결 되어라… 되었다!
자 이제 운동 시작!
집에 실내용 자전거가 있어서 10분짜리 코스로 운동을 했다
음 일단 애플 피트니스 플러스가 너무 좋았던 이유 중 하나는 왼쪽 상단, 오른쪽 상단에 애플 워치 정보가 표시된다. 운동을 몇분 했고 나의 심박수가 현재 몇이고 칼로리를 얼마나 소비를 했는지 바로 보여준다. 그리고 오른쪽 상단에서는 운동링이 보여지는 데 저게 채워지는 과정을 볼 수 있어서 뭔가 게이미케이션 한 요소가 있어서 자극이 되었다.
운동을 마치고 나면 결과를 보여준다. 총 칼로리 소비량, 운동시간, 심박수 등등 다양한 정보가 나온다. 뭔가 오늘 하루 운동을 했다는 기록을 남긴 것 같아서 뿌듯함을 느낄 수 있다.
스트레칭 같은 운동도 있다근력운동 30분 코스. 이건 정말 빡세다. 바벨 필수운동 결과를 보고나니 뿌듯 뿌듯…
마무리
애플은 참 서비스를 잘 만든다는 생각이 들었다. 코로나 때문에 1년치 등록한 헬스장을 못가고 있어서 답답했는데 애플 피트니스를 통해 집에서 운동을 하니 마치 헬스 트레이너가 앞에 있는 느낌이 들었고 운동의 종류도 너무나 다양해서 재미있었다. 피트니스 플러스 가격은 월 9.99 달러이지만 가족공유를 하면 5명까지 초대를 할 수 있고 즉 5명이서 월 9.99불에 사용할 수 있기 때문에 가격도 괜찮은 것 같다.
음 그리고 애플워치를 아주 잘 활용한 서비스인 것 같다. 운동 종류마다 칼로리 소비량이 다른데 그것을 알아서 기록해줘서 사용자는 뭔가 기록을 위해 신경쓸 필요가 없다. 그냥 운동 종목 선택, 운동만 하면 되기 때문에 상당히 편리하다.
아 참, 애플 피트니스에는 운동 뿐만 아니라 댄스도 있어서 춤을 배우고 싶은 사람들한테도 추천.
To support HTTPS, We need to create a certificate. I’ll use an AWS certificate.
Add your domain name. e.g., *.yourDomain.com
That’s it. Let’s create Load Balancer
Create AWS Load Balancer
The load balancer connects to the ECS service through a fixed DNS. If you have your domain address, you can register Load Balancer’s DNS as CNAME and use it.
First, select Load Balancing from the list of EC2 services.
This part is essential. Let’s add HTTPS.
Please set up the VPC of the ECS created in the previous post.
I choose the certificate which is made on AWS.
Leave only 0.0.0/0 in Source and delete the ‘::/0’. ‘::/0’ is used by ipv6.
The vapor app uses 8080 as the default port. Let’s set the routing port to 8080.
A health check is a path that periodically checks whether the server is operating correctly. Health Check is straightforward to set up in the Vapor app.
//Set HealthCheck
app.get("health") { req in
return "OK"
}
Whether the server is running correctly every 120 seconds, that is, every 2 minutes.
The setting is done. Simple, isn’t it?
Don’t forget to check if the Target Group of Load Balancer is also adequately created.
Create ECS Service
Let’s create a new service that runs on ECS Container. I’ll set a load balancer for it.
The tasks are pending because our server can’t access the RDS. I’ll resolve it by setting the inbound rule for RDS.
Setup inbound of RDS
Now the tasks that run on ECS are working fine.
Setup ‘forward rule’ on Load Balancer
To access your server, You should set up the ‘forward rule’ on LoadBalancer. Do you remember our load balancer listens to two-port? HTTP and HTTPS.
Let’s set up the ‘forward rule.’
Check your Load Balancer. You can see there is two Listener ID.
Set HTTPS, Forward to ECS Clusters load balancing target group.
Set HTTP, Forward to ECS Clusters load balancing target group. (Yes, It’s the same as HTTPS)
That’s all. Now you can access your Vapor Server by using the DNS name. Copy and Pasted it to your web browser and then check whether it can access or not.
Setup your DNS Name, CName on Namecheap.com
Do you have your domain? The last step is to set up your domain with an AWS load balancer and certificates.
Go to AWS Certificate Manager. Check your CNAME and Value.
I’m using NameCheap.com. So If you bought a domain at NameCheap.com, then go to the advance DNS setup at NameCheap.com.
You have to register two CNAME records. One is AWS Certificates the other one is Load Balancer’s DNS.
AWS Certificates
AWS Certificate Manager -> Domain
Load Balancer’s DNS
EC2 -> Load Balancers -> Description -> DNS Name
Conclusion
I introduced how to set up your vapor app using the Load Balancer. There are many steps you have to set up. I don’t think It’s complicated. Once you set it up, then you can easily set up your vapor app next time. I hope you enjoy reading my posts.
This post will write about Docker, AWS ECR, ECS, Fargate, RDS, and Github Action.
Contents
Overview
Getting Start AWS
Step 1. Create AWS Account
Step 2. Create IAM Account
Create Amazon ECR
Create Amazon ECS
Create Amazon RDS
Deploy: Push your Docker Image
Option 1. AWS Command line tool on macOS
Fargate Task Pending Issue!!
Try to access the Vapor app on Web Browser
Option 2. Github Action
MEMO
I’m not a DevOps or backend engineer. Before posting it, I read books about AWS and Server-Side Swift and tutorials and took advice from friends who work as backend engineers.
Overview
To deploy the vapor docker container, I’ll use AWS services.
You can push your docker image to the ECR. And then, ECS pull your docker image by setting the ECR URI. ECS launch docker container on AWS. Also, It has integration with Load Balancer. The Fargate is a task of ECS. It is responsible for offering serverless. You can set memory and CPU for containers.
Getting Start AWS
Step 1. Create AWS Account
If you haven’t an account yet, let’s create a new account.
Now You have an AWS account. This account is called a root account.
Step 2. Create IAM Account
Log in as root account. And then search the IAM at the top of the search bar.
Before creating IAM Account, Let’s make the group for users. I captured all of the steps. You just followed the steps below.
Create Groups
Enter the group name you want to use it.
You created the group. As you can see, there are no Users in the group. Let’s create an IAM account and add it to the group we made.
Create IAM Account
Don’t forget to download .csv file. To access AWS using the CLI tool, you need to set accessKey, secretKey, and console login link.
Now You have an IAM Account. In the AWS document, You should not use the root account for creating the other services. You can use the root account when managing users (IAM service) and billings.
That’s all about the root account’s role. Please log out root account now. And login with an IAM account you created to create an ECR, ECS, Fargate, and RDS services.
If you forgot the accountID(12 digits), you log in as a root account and check your accountID(12 digits).
You will see your 12 digits of account id in the blue box above the screenshot.
Create Amazon ECR
ECR is a repository for Docker Images like Docker Hub. You can push and pull your Docker images and set version tags. The default setting is private, and also you can select a public repository.
Search the ECR at the top of the search bar and create a repository.
Set the repository name you want.
That’s it. Click the Create repository button.
Wait a few seconds. You will see the repository you created. Now You are ready to push your Docker image to the ECR.
Create Amazon ECS
Now You need to create an ECS cluster and task to run your Docker Image in ECR.
Copy your ECR URI. (We will use this URI:latest as an image URI)
Select the ECS Clusters and Click the Get started button.
Paste your ECR URI you copied. e.g., ecrUri:latest
Expand the Advanced Container Configuration. Scroll down to Environment section. Remove the default settings in the blue box above the screenshot.
Enter the task definition name you want.
Create Amazon RDS
To set up a database for your vapor app, You have to create an RDS service database. Search for the RDS keyword at the top of the search bar.
I’ll use PostgreSQL. (Select database you want to use for vapor app)
Set your ECS VPC. The subgroup is automatically updated when you select the VPC.
Create New VPC security group to set the inbound for PostgreSQL default port 5432.
That’s it. You can check the database you create.
Deploy: Push your Docker Image
I’ll show you how to push your Docker image to the ECR. There are two options. Choose the option you like.
# Build image
FROM swift:5.3.2-focal as build
RUN apt-get update -y \
&& apt-get install -y libsqlite3-dev
WORKDIR /build
COPY . .
RUN swift build \
--enable-test-discovery \
-c release \
-Xswiftc -g
# Run image
FROM swift:5.3.2-focal-slim
RUN useradd --user-group --create-home --home-dir /app vapor
WORKDIR /app
COPY --from=build --chown=vapor:vapor /build/.build/release /app
COPY --from=build --chown=vapor:vapor /build/Public /app/Public
COPY --from=build --chown=vapor:vapor /build/Resources /app/Resources
# ARS RDS Environment ARG
ARG AWS_RDS_HOST
ARG AWS_RDS_PORT
ARG AWS_RDS_USER
ARG AWS_RDS_PASS
ARG AWS_RDS_DB
# SignInWithApple Environment ARG
ARG SIWA_ID
ARG SIWA_REDIRECT_URL
ARG SIWA_JWK_ID
ARG SIWA_PRIVATE_KEY
ARG SIWA_TEAM_ID
ARG SIWA_APP_BUNDLE_ID
# Set Environment
RUN echo "SIWA_ID=${SIWA_ID}" > .env.production
RUN echo "SIWA_REDIRECT_URL=${SIWA_REDIRECT_URL}" >> .env.production
RUN echo "SIWA_JWK_ID=${SIWA_JWK_ID}" >> .env.production
RUN echo "SIWA_PRIVATE_KEY=${SIWA_PRIVATE_KEY}" >> .env.production
RUN echo "SIWA_TEAM_ID=${SIWA_TEAM_ID}" >> .env.production
RUN echo "SIWA_APP_BUNDLE_ID=${SIWA_APP_BUNDLE_ID}" >> .env.production
RUN echo "DB_HOST=${AWS_RDS_HOST}" >> .env.production
RUN echo "DB_PORT=${AWS_RDS_PORT}" >> .env.production
RUN echo "DB_USER=${AWS_RDS_USER}" >> .env.production
RUN echo "DB_PASS=${AWS_RDS_PASS}" >> .env.production
RUN echo "DB_NAME=${AWS_RDS_DB}" >> .env.production
USER vapor
# Export Port
EXPOSE 8080
ENTRYPOINT ["./Run"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]
The code is my Dockerfile for the Vapor app. I’ll write the post about server-side swift. In this post, I focused on deploying the Docker Image to AWS. Don’t forget to export 8080.
Option 1. AWS Command line tool on macOS
Install AWS cli tool on macOS
Download the pkg file and install it. You don’t need to set the installation path. Just click the continue button until the installation is completed.
Open the terminal and check the CLI tool.
Deploy the Docker image using the CLI tool.
Let’s create a `deploy.sh` file in the path which has Dockerfile.
Set your containerName and region. You can check these names on Amazon ECR.
Step 1. You can check the access key and secret key in the CSV file of the IAM Account you create.
Step 3. Check your RDS values on the RDS Database settings.
sh ./deploy.sh
Let’s run the deploy script and check the result. There is no issue, and You pushed the Docker image to the ECR successfully.
You can see the image you pushed. If you want to check the command for push image using CLI, click the View push commands above the screenshot.
Fargate Task Pending Issue!!
You pushed the Docker image to the ECR. The ECS Fargate task is automatically started to run the Vapor container. Let’s check the Fargate task’s status. You may find-out the problem. Its status is pending. Why?
You didn’t set the inbound rule for your ECS VPC. Let’s put the inbound rule of the security group on RDS for ECS VPC.
Add custom TCP 5432 PORT. Don’t forget to select the source as your ECS Container Security Group.
Let’s recheck the Fargate task. Its status is updated to Running. You resolve the RDS accessing problem!
You can also check the log in the CloudWatch service. Your server is running now.
Try to access the Vapor app on Web Browser
Let’s check the public IP and access the Vapor app on the Web Browser.
Hmm, You can’t access the server. The problem is that you didn’t allow the 8080 port in the Cluster’s security group.
Let’s resolve this issue.
That’s all. Now You can access the vapor app on Safari. Let’s recheck it.
You resolve the access problem!
Option 2. Github Action
You can also set the deploy Docker Image using Github Action. Let’s set up. Before reading this section, I have to say that the setting of Github Action has one issue. I have almost done it except building Docker Image. If you want to try my setup, let’s keep follow my instruction.
Click the New workflow on your GitHub repository.
I’ll use the Deploy to Amazon ECS template.
In the YML file, You can see the ${{ secrets.KEY }}. This KEY is Secret Environment Variables. You can set the values on your repository Settings tap.
Set the Environment Variable Name and Value.
That’s it.
# This workflow will build and push a new container image to Amazon ECR,
# and then will deploy a new task definition to Amazon ECS, when a release is created
#
# To use this workflow, you will need to complete the following set-up steps:
#
# 1. Create an ECR repository to store your images.
# For example: `aws ecr create-repository --repository-name my-ecr-repo --region us-east-2`.
# Replace the value of `ECR_REPOSITORY` in the workflow below with your repository's name.
# Replace the value of `aws-region` in the workflow below with your repository's region.
#
# 2. Create an ECS task definition, an ECS cluster, and an ECS service.
# For example, follow the Getting Started guide on the ECS console:
# https://us-east-2.console.aws.amazon.com/ecs/home?region=us-east-2#/firstRun
# Replace the values for `service` and `cluster` in the workflow below with your service and cluster names.
#
# 3. Store your ECS task definition as a JSON file in your repository.
# The format should follow the output of `aws ecs register-task-definition --generate-cli-skeleton`.
# Replace the value of `task-definition` in the workflow below with your JSON file's name.
# Replace the value of `container-name` in the workflow below with the name of the container
# in the `containerDefinitions` section of the task definition.
#
# 4. Store an IAM user access key in GitHub Actions secrets named `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.
# See the documentation for each action used below for the recommended IAM policies for this IAM user,
# and best practices on handling the access key credentials.
on:
push:
branches: [ master ]
name: Deploy to Amazon ECS
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-2
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_CONTAINER_NAME: ${{ secrets.AWS_ECS_CONTAINER_NAME }}
IMAGE_TAG: latest
run: |
# Build a docker container and
# push it to ECR so that it can
# be deployed to ECS.
docker build \
--build-arg SIWA_ID=${{ secrets.SIWA_ID }} \
--build-arg SIWA_REDIRECT_URL=${{ secrets.SIWA_REDIRECT_URL }} \
--build-arg SIWA_JWK_ID=${{ secrets.SIWA_JWK_ID }} \
--build-arg SIWA_PRIVATE_KEY=${{ secrets.SIWA_PRIVATE_KEY }} \
--build-arg SIWA_TEAM_ID=${{ secrets.SIWA_TEAM_ID }} \
--build-arg SIWA_APP_BUNDLE_ID=${{ secrets.SIWA_APP_BUNDLE_ID }} \
--build-arg AWS_RDS_HOST=${{ secrets.AWS_RDS_HOST }} \
--build-arg AWS_RDS_PORT=${{ secrets.AWS_RDS_PORT }} \
--build-arg AWS_RDS_USER=${{ secrets.AWS_RDS_USER }} \
--build-arg AWS_RDS_PASS=${{ secrets.AWS_RDS_PASS }} \
--build-arg AWS_RDS_DB=${{ secrets.AWS_RDS_DB }} \
-t $ECR_REGISTRY/$ECR_CONTAINER_NAME:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_CONTAINER_NAME:$IMAGE_TAG
echo "::set-output name=image::$ECR_REGISTRY/$ECR_CONTAINER_NAME:$IMAGE_TAG"
I modified the YML file refer to our deploy.sh file. When you commit to the master branch, then Github Action is started. Let’s check the results.
Almost steps are done. But The Docker build step is failed.
In the build step, the docker command can’t find the Dockerfile. If you use the `uses: docker/build-push-action@v2` in YML, You can successfully build your Dockerfile. But `uses: docker/build-push-action@v2` is trying to push docker hub, not AWS ECR, even if I set the ECR URI. If you know to resolve this issue, please let me know. I’ll update the Github Action step soon!
In this post, I’ll explain the life cycle of UIViewController.
What happens when you create the UIViewController?
loadView (only once called during the entire life cycles)
viewDidLoad (only once called during the entire life cycles)
viewWillAppear
viewIsAppearing
viewWillLayoutSubviews
viewDidLayoutSubviews
viewDidAppear
And what happens when you dismiss or destroy the UIViewController?
viewWillDisappear
viewDidDisappear
There are many steps of UIViewControllers. Let’s take a look loadView method.
loadView
Every view controller which subclassing the UIViewController has the view. It is the root view of the view controller. The default value is nil, and loadView is called when a view is a nil.
There are three ways to initialize the UIViewControllers.
Use Nib
Use Storyboard
Use Code
You take care of loadView methods. If your view controller uses the Nib or Storyboard, then you should not override this method.
Why?
As I mentioned, You can initialize the UIViewController in different ways. If you use the Nib or Storyboard, the view will be set when loaded from Nib or Storyboard objects.
Nib
The init(nibName:bundle:) will set the view by loading the nib file.
Storyboard
The initiateViewController(withIdentifier:) will set the view. (UIStoryboard object create the view controller and return it to your code)
Code
You should override the loadView method to set the root view of the view controller. You can also put subviews into the root view on this method. Don’t call a super.loadView in the override loadView method.
override func loadView() {
let rootView = UIView()
rootView.backgroundColor = .blue
view = rootView
//You can also add some more subviews into the view
}
ViewDidLoad
It is called when the view controller’s view hierarchy is loaded into the memory.
If you are using Storyboard or Nib, You can add, remove subviews on ViewDidLoad methods.
Use this method to add or remove views, modify layout constraints, and load data for your views.
viewWillAppear
This method is called before configuring any animations and adding to the view hierarchy to display. You can set the style of view or set or change the orientation of the status bar. Call a super.viewWillAppear is required.
viewWillLayoutSubviews
When the view controller’s view is changed in its layout or bounds, then the system will call a viewWillLayoutSubviews. Compare to viewDidAppear; It will be automatically called when the view’s frame or bound changed, which means it can be called several times. On the other hand, viewDidAppear called once when the view controller’s view shows on the screen.
Your view controller can override this method to make changes before the view lays out its subviews. The default implementation of this method does nothing.
viewDidLayoutSubviews
This method is called when after layout subViews. It means that the view is updated and lays out their bounds and frames. You can override this method if you need some implementation when after updated views. And You don’t need to call a super.viewDidLayoutSubviews because the default implementation does nothing.
When the bounds change for a view controller’s view, the view adjusts the positions of its subviews and then the system calls this method. However, this method being called does not indicate that the individual layouts of the view’s subviews have been adjusted. Each subview is responsible for adjusting its own layout.
updateViewConstraints
If you use autolayout, You can override this method to update the view’s constraints. This method will be called when you call a view.setNeedsUpdateConstraints().
What happens when you call a setNeedsUpdateConstraint?
viewWillLayoutSubviews
updateViewConstraints
viewDidLayoutSubviews
Here are examples
@IBOutlet weak var buttonTopConstraint: NSLayoutConstraint!
override func updateViewConstraints() {
//Implement your code in here
buttonTopConstraint.constant = 100
//You should call a super.updateViewConstraints at final step
super.updateViewConstraints()
}
Hmm, I watched the WWDC 15 video about this. I’m not sure should I need to override this function or not. Apple also recommends setting constraints in a place when you need to change it. (e.g., When tapped the button then update the constraint, set a constraint in an IBAction method) If you are facing a performance problem to update the constraints, then you can override updateViewConstraints.
Override this method to optimize changes to your constraints.
It is almost always cleaner and easier to update a constraint immediately after the affecting change has occurred. For example, if you want to change a constraint in response to a button tap, make that change directly in the button’s action method.
You should only override this method when changing constraints in place is too slow, or when a view is producing a number of redundant changes.
Your implementation must be as efficient as possible. Do not deactivate all your constraints, then reactivate the ones you need. Instead, your app must have some way of tracking your constraints, and validating them during each update pass. Only change items that need to be changed. During each update pass, you must ensure that you have the appropriate constraints for the app’s current state.
viewDidAppear
It is called when after all of the views in view controller are onScreen. Call a super.viewDidAppear is required.
viewWillDisappear
It is called the view being removed from the view hierarchy. You can use this function to resign the responder or revert the orientation or style of view set on viewWillAppear or viewDidAppear. Call a super.viewWillDisappear is required.
viewDidDisapper
It is called the view was removed from the view hierarchy. Use this function if you need some tasks when dismissing the view controller. Call a super.viewDidDisappear is required.
In this tutorial, you’ll learn how to separate logics from view and to deal with user input on UITextField by making the calculator app. In this project, users can input the bill and tip, and it automatically displays the result of the total amount and tip amount when entering the bill or tip. You can also learn how to view the number of human-friendly formats.
In the process, you’ll learn:
Designing the calculator app using storyboard and xib
Rendering the xib in storyboard by using IBDesignable and IBInspectable
Dealing with input using the UITextFieldDelegate and NumberFormatter
Reflect the result of the total amount and tip amount using the protocol.
Key Topics
Storyboard
IBOutlet / IBAction
Delegate Pattern
NumberFormatter
Designing the calculator app using storyboard and xib
This calculator app has four components, which are two input fields and two result views. As you can see, the elements look similar. Isn’t it? It consists of UILabel, UITextField, and UIStepper. Ok, making the CustomView for a component using xib is the right choice rather than layout the all separated UILable, UITextField, and UIStepper 4 times into the Storyboard.
Let’s create our InputView.
⌘ + N -> Source -> Swift File
⌘ + N -> User Interface -> View
We created the Swift and Xib File. Let’s set up xib.
We use the StackView to layout the item horizontally. The UILabel is set by width constraint relative to SuperView’s width and multiply the 0.15, which means UILabel’s width will place 15% of SuperView’s width.
Look at Placeholders. The File’s Owner is pointing to InputView. The IBOutlet and IBAction are also linked to the InputView class.
In the InputView.xib, open the Assistant for linking IBOutlet and the IBAction. When you link between xib component and InputView.swift, you will see the result like the image below.
We almost did for setting InputView. Let’s write the code.
import UIKit
@IBDesignable
final class InputView: UIView {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var inputTextField: UITextField!
@IBOutlet weak var tipStepper: UIStepper!
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
}
//It called when you are using the xib for creating it.
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
//It called when you create view programmatically.
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
private func setupView() {
guard let nib = loadNib() else {
return
}
nib.translatesAutoresizingMaskIntoConstraints = false
addSubview(nib)
NSLayoutConstraint.activate([
nib.leadingAnchor.constraint(equalTo: self.leadingAnchor),
nib.trailingAnchor.constraint(equalTo: self.trailingAnchor),
nib.topAnchor.constraint(equalTo: self.topAnchor),
nib.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
private func loadNib() -> UIView? {
let bundle = Bundle(for: Self.self)
return bundle.loadNibNamed(String(describing: Self.self), owner: self, options: nil)?.first as? UIView
}
@IBAction func didUpdateStepper(_ sender: UIStepper) {
inputTextField.becomeFirstResponder()
inputFormatter?.updateTipValue(inputTextField, value: sender.value)
}
}
In the InputView.swift, We have two init function which is mandatory for making subView. To set subViews, we should load xib from Bundle, which organizes the code and resources in the Bundle directory. Then add subView into the InputView using autolayout.
We use the @IBDesignable to load view on Storyboard. If not, the Storyboard can’t load our view. The @IBAction is aiming for handling the event like a tap, touchdown, and user actions. We linked the action from UIStepper to handle value changed event.
Rendering the xib in storyboard by using IBDesignable and IBInspectable
As we already set @IBDesignable in InputView for displaying the view on the Storyboard. Let’s open the Storyboard to set the views.
You can see our custom UIView called InputView is rendered on the Storyboard. Now I’ll explain the @IBInspectable for setting details on the Storyboard.
Open the InputView.swift and add the @IBInspectable.
@IBInspectable var title: String? = nil {
didSet {
titleLabel.text = title
}
}
@IBInspectable var placeHolder: String? = nil {
didSet {
inputTextField.placeholder = placeHolder
}
}
@IBInspectable var editable: Bool = true {
didSet {
inputTextField.isEnabled = editable
}
}
@IBInspectable var tipInput: Bool = false {
didSet {
if tipInput {
tipStepper.isHidden = false
}
}
}
After adding the @IBInspectable var in InputView.swift, then open the Storyboard and check the InputView component.
We added the four @IBInspectable, and you can set the values on the Storyboard. When you set the value, then It will reflect the value and rendering again.
Dealing with input using the UITextFieldDelegate and NumberFormatter
We will create the InputFormatter class to handle input from the keypad. To separating the logic from View, It handles the InputView’s UITextField event bypassing the delegate.
class InputFormatter: NSObject {
enum InputType {
case tip
case bill
}
private var inputType: InputType = .bill
private lazy var formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 0
if self.inputType == .bill {
formatter.numberStyle = .currency
formatter.currencyCode = Locale(identifier: "en-US").currencyCode
}
else if self.inputType == .tip {
formatter.numberStyle = .percent
formatter.multiplier = 1
}
return formatter
}()
override init() {
super.init()
}
convenience init(type: InputType) {
self.init()
inputType = type
}
private func setupKeyboardType(_ textField: UITextField) {
textField.keyboardType = .numberPad
}
}
extension InputFormatter: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
if textField.keyboardType == .default {
setupKeyboardType(textField)
}
return true
}
func textField(
_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String
) -> Bool {
guard let text = textField.text else {
return true
}
if let formattedString = convertFormattedString(text, replacementString: string) {
textField.text = formattedString
}
return false
}
}
}
This class has convenience init to handle inputType, whether tip or bill. For the tip, the NumberFormatter’s number style is percent to display the number with percentage symbol. For the bill, It will display the number with a currency symbol.
Look at the UITextFieldDelegate functions. I handle the input stream from the number keypad and convert it for a human-readable format using the convertFormattedString function, which uses the NumberFormatter. (I attached full source code in this article. You can check it the function)
Let’s take a look above function. In this function, we can check the input string from the ‘replacementString’ parameter. It will be number because we set the keyboard type as numberPad. To use NumberFormatter to convert all input, we should return false to set the textField.text.
class ViewController: UIViewController {
@IBOutlet weak var resultStackView: UIStackView!
@IBOutlet weak var resultTitleLabel: UILabel!
@IBOutlet weak var tipResultView: InputView!
@IBOutlet weak var totalResultView: InputView!
@IBOutlet weak var inputStackView: UIStackView!
@IBOutlet weak var inputTitleLabel: UILabel!
@IBOutlet weak var billInputView: InputView!
@IBOutlet weak var tipInputView: InputView!
override func viewDidLoad() {
super.viewDidLoad()
resultStackView.setCustomSpacing(40, after: resultTitleLabel)
inputStackView.setCustomSpacing(40, after: inputTitleLabel)
billInputView.delegate = InputFormatter(type: .bill)
tipInputView.delegate = InputFormatter(type: .tip)
}
}
The ViewController code is pretty simple. You can just set the delegate in viewDidLoad.
Reflect the result of the total amount and tip amount using the protocol
We are almost done. The final step reflects the result of an amount when entered bill or tip automatically. To do that, let’s create an AmountPresentable protocol and InputFormatterDelegate.
We added the InputFormatterDelegate in InputFormatter and then conformed it in ViewController. It will let ViewController when detecting a typing event.
To calculate the total amount and tip amount, We add AmountPresentable protocol.
protocol AmountPresentable {
var billValue: NSNumber { get }
var tipValue: NSNumber { get }
var tipPercentage: NSNumber { get }
var formatter: NumberFormatter { get }
func updateTipAmount(assign to: InputView)
func updateTotalAmount(assign to: InputView)
func updateStepperValue(assign to: InputView)
}
extension AmountPresentable {
func updateTipAmount(assign to: InputView) {
let tipAmount = formatter.string(from: self.tipValue)
to.inputTextField.text = tipAmount
}
func updateStepperValue(assign to: InputView) {
to.tipStepper.value = tipPercentage.doubleValue
}
func updateTotalAmount(assign to: InputView) {
let totalValue = billValue.doubleValue + tipValue.doubleValue
let totalAmount = formatter.string(from: NSNumber(value: totalValue))
to.inputTextField.text = totalAmount
}
}
This protocol binds to InputView to display the amount. It is called in the ViewController in the didUpdateAmount function. Let’s conform to the AmountPresentable on ViewController.
class ViewController: UIViewController, AmountPresentable {
var billValue: NSNumber = 0
var tipValue: NSNumber = 0
var tipPercentage: NSNumber = 0
//…
override func viewDidLoad() {
super.viewDidLoad()
//…
billInputView.delegate = InputFormatter(type: .bill, delegate: self)
tipInputView.delegate = InputFormatter(type: .tip, delegate: self)
}
}
extension ViewController: InputFormatterDelegate {
func didUpdateInput(_ type: InputFormatter.InputType, number: NSNumber) {
if type == .tip {
tipValue = NSNumber(value: billValue.doubleValue * (number.doubleValue * 0.01))
tipPercentage = number
}
else if type == .bill {
billValue = number
tipValue = NSNumber(value: billValue.doubleValue * (tipPercentage.doubleValue * 0.01))
}
updateTipAmount(assign: tipResultView)
updateTotalAmount(assign: totalResultView)
updateStepperValue(assign: tipInputView)
}
}
That’s all. When received the didUpdateInput function, then update the amount by using AmountPresentable. It enables us to reflect the amount during the enter the bill or tip.
Conclusion
We learned how to use Storyboard and Xib, UITextField, and Delegate patterns. We can improve our code logic if we use the combine framework, but It only supports iOS 13.0. If you understand this project and how delegation and protocol works, you can also quickly adapt the combined framework. I hope you enjoy this post. Thank you!
In this post, I’ll share useful English resources for Software Engineer. I struggled to express my thought to someone. It’s always challenging me. I decided to find English books focused on the Software Industry because There are many useful sentences for software engineers. It helps me a lot and saves me time.
Top 50 Software Engineer Personal Interview Questions & Answers
I highly recommend this book. In this book, there are great examples of answers to personal interview questions. You can pick a sentence and rephrase it for your stories.
Cracking the Behavioral Interviews: for Software Engineers
It’s similar to Knowledge Powerhouse. The author is a co-founder of TechMockInterview.com. In this book, 28 questions and answers. The answers are very detailed and useful sentences to express your software engineer careers.
Express Publishing published a series of career paths. It’s one of the career path books focused on the Software Engineering domain. I highly recommend it. It looks like side by side book. There are many useful chapters to learn Business English for Software Engineer.
I highly recommend it. You can learn how to use STAR(Situation, Tasks, Action, Result) strategies to express your story in this course. Also, Terry answered my questions very friendly.
Unlike Pass your job interview in English, It’s more focused on business English for daily life in the office. I learnt how to communicate with colleagues during meetings, business trips, etc.
PRAMP is a free service for practicing a mock interview for software engineers. You can not only practicing coding interviews but also behavior interviews. There is no limit to practices. You can also get feedback from your interviewer for free.
Learn English from Conference
There are many conferences for Software Engineers. Pick your favorite conference and watch and repeat it a lot. You can learn how to give a speech to your thought.
As an iOS engineer, WWDC is such a great conference in the year for me. Apple delivered a WWDC application. In this app, You can watch all the videos, and it provides the transcriptions!
Conclusion
For ESL software engineer, English is the most challenging. But I have to learn English to expand my career in the tech industries. I’m a native Korean and living in Seoul for over 30 years. I have never lived outside of Korea. Although it is challenging to access English, as I introduced you, I believe that you can learn English if you have the will through various media. Even if I am not good at English, I continue to run blogs in English to expand my career and to communicate with English-speaking engineers by sharing my knowledge.
내가 찾아간 곳은 브루클린 지점이고 여기는 예약이 필수이기 때문에 한국에서 미리 예약을 하고 찾아갔다. 예약은 피터루거 홈페이지에서 하면 된다.
피터루거 스테이크 하우스
이날은 스테이크를 먹는다는 생각에 들떴었는지 사진을 생각보다 많이 안찍었다. 1층 로비에 들어가면 대기할 수 있는 Bar 같은 공간이 있고 간단한 음료나 술을 주문해서 먹을 수 있다. 나는 저녁 8시인가로 예약을 했었는데 그날 일정이 생각보다 빨리 끝나서 예약했던 시간보다 1시간 정도 더 빨리 도착했다.
종업원한테 예약시간 보다 조금 빨리 왔다고 말했더니 조금만 기다리라고 말하더니 한 10분 지났을까? 자리 마련했다고 들어오라고 했다.
피터루거 스테이크 메뉴
식전으로 나온 빵, 맛있었지만 스테이크로 배를 채워야 되니까 자제했다.
드디어 메인 메뉴 스테이크가 나왔다. 나는 T본 스테이크를 시켰고 미디엄 웰던으로 주문했다. 입에서 살살 녹는 맛이었다. 진짜 진심 맛있었다!
스테이크는 굽기에 따라서 미디엄웰던이면 고기위해 흰색 소를 올려두고 레어로 주문하면 빨간색 소를 스테이크위에 올려서 가져온다. 피터루거는 미디엄 웰던이 보통 다른 스테이크 레스토랑의 미디엄 레어 정도 굽기인 것 같다.
일단 고기가 엄청 두껍고 질긴 부분이 하나도 없었다. 스테이크를 먹다보면 힘줄 같은 것때문에 질긴 부분이 있을 수 있는데 피터루거 스테이크는 그런 부위가 전혀 없었다.
직원들의 친절도에 대한 이야기가 후기에 보면 다녀온 사람마다 다른것 같은데 우리가 갔던날 직원들은 다들 엄청 친절했었다. 그리고 토마토도 스테이크랑 참 잘 어울리는 조합이었다.
You must be logged in to post a comment.