Seoullo IOS App

서울로 관광 애플리케이션

개요

많은 정보를 얻기 위해선 직접 검색을 하여 홈페이지에 들어가야 하는 번거로움을 없애고자 ‘서울로7017’에 대한 모든 관광 정보를 직관적으로 보여주고, 위치 기반 인증사진으로 서울로에 대한 커뮤니티를 구현한 애플리케이션.

프로젝트 기간

2017년 10월 1일 ~ 2017년 10월 31일 (1개월) - iPhone 4 개발

2017년 11월 21일 ~ 2017년 12월 5일 (2주) - iPhone X 개발

프로젝트 등급

2017 서울시 앱 공모전 출품 (알파테스트) , 현재 Server Off

IOS.ver 11.0

GitHub Repository

https://github.com/godpp/seoullo_ios

워크플로우



개발설명

로그인 화면

키보드가 올라왔을 때 아이디, 패스워드 입력 창이 가리지 않도록 CenterConstraintY를 Outlet으로 생성해 임의로 중심을 조정해 주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func keyboardWillShow(notification: NSNotification) {
if check {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
backCenterConstrainY.constant = -130
centerConstraintY.constant = 0
check = false
view.layoutIfNeeded()
}
}
}
func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
centerConstraintY.constant = 130
backCenterConstrainY.constant = 0
check = true
view.layoutIfNeeded()
}
}

메인화면

SegmentedControl을 커스텀할 필요가 있었기 때문에, 동적으로 생성해 아래와 같은 코드로 커스텀하여 시설, 조경, 주요지점의 세 지도 이미지뷰로 분할하였다.

1
2
3
4
5
6
7
8
9
10
segmentedControl.backgroundColor = UIColor.white
segmentedControl.tintColor = UIColor.clear
segmentedControl.setTitle("시설", forSegmentAt: 0)
segmentedControl.setTitle("조경", forSegmentAt: 1)
segmentedControl.setTitle("주요지점", forSegmentAt: 2)
segmentedControl.setTitleTextAttributes([NSForegroundColorAttributeName : UIColor.white], for: .selected)
segmentedControl.setTitleTextAttributes([NSForegroundColorAttributeName : UIColor.init(red: 0/255.0, green: 157/255.0, blue: 76/255.0, alpha: 1.0)], for: .normal)
segmentedControl.setBackgroundImage(UIImage(named:"mypage_segment_white.png"), for: .normal, barMetrics: UIBarMetrics.default)
segmentedControl.setBackgroundImage(UIImage(named: "mypage_segment_green.png"), for: .selected, barMetrics: UIBarMetrics.default)
segmentedControl.frame = CGRect(x: 0, y: 245, width: 375, height: 44)

지도이미지에서의 관광지 위치는 버튼처리를 하였는데, 다수의 불규칙적인 버튼 위치 때문에 정적으로 위치와 Constraint를 잡아주었다.

하단의 ‘서울로7017 시설랭킹’에는 실시간으로 사용자들의 시설 좋아요 순위를 반영해 누적된 좋아요 개수를 내림차순으로 정렬해 제일 높은 순위부터 3개를 이미지와 이름을 Label로 띄워주었다. 사각형 이미지의 둥근 원 처리는 아래와 같은 코드로 구현하였다.

1
2
firstRankImg.clipsToBounds = true
firstRankImg.layer.cornerRadius = firstRankImg.frame.size.width / 2

메인화면의 최하단에는 3개의 탭바로 구성되어있고, 좌측은 서울로에서 진행되는 프로그램을 보여주는 ‘서울로 정보’, 우측은 서울로7017의 이용자들이 직접 올리는 인증샷들을 한번에 보여주는 ‘#서울로’로 구성하였고, 중간에는 메인화면으로 돌아올수 있는 홈을 구성하였다. Xcode의 탭바 구성상 첫 화면은 맨 좌측탭으로 이동하기 마련인데 아래와 같은 코드로 첫 화면이 중간에 오게 구현 및 커스텀을 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Main_Tab : UITabBarController{

@IBInspectable var defaultIndex: Int = 1

override func viewDidLoad() {

selectedIndex = defaultIndex

UITabBar.appearance().barTintColor = UIColor.init(red: 255.0/255.0, green: 255.0/255.0, blue: 255.0/255.0, alpha: 0.0)
var tabBar = self.tabBar

var mainImage = UIImage(named:"icon_home.png")?.withRenderingMode(.alwaysOriginal)
var infoImage = UIImage(named: "tabbar_information.png")?.withRenderingMode(.alwaysOriginal)
var hashImage = UIImage(named: "tabbar_camera.png")?.withRenderingMode(.alwaysOriginal)

(tabBar.items![0] as! UITabBarItem).image = infoImage
(tabBar.items![1] as! UITabBarItem).image = mainImage
(tabBar.items![2] as! UITabBarItem).image = hashImage
(tabBar.items![0] as! UITabBarItem).selectedImage = infoImage
(tabBar.items![1] as! UITabBarItem).selectedImage = mainImage
(tabBar.items![2] as! UITabBarItem).selectedImage = hashImage

(tabBar.items![0] as! UITabBarItem).index(ofAccessibilityElement: 0)
(tabBar.items![1] as! UITabBarItem).index(ofAccessibilityElement: 1)
(tabBar.items![2] as! UITabBarItem).index(ofAccessibilityElement: 2)

}
}

관광지 정보 & 후기 화면

메인화면에서 지도이미지뷰 위의 관광지를 클릭하면 볼수있는 관광지 정보 화면이다. 중단 SegmentedControl을 이용해 정보, 후기로 뷰를 나누었고, 특히 후기 뷰는 Container View를 활용해 따로 뷰를 빼서 TableView를 구현하였다.

특히 후기탭에 후기 작성 버튼을 플로팅 버튼으로 만들어 쉽게 작성가능하도록 만들었으며, 후기 작성 뷰는 모달식으로 구성하였다.

후기 작성 뷰에서 사진을 업로드하면 ‘#서울로’ 탭바에 자동으로 모든 사용자의 사진이 모아진다.

#서울로 인증샷 모아보기

후기 작성할때 관광지 인증샷을 올리면 모든 사용자의 인증사진을 모아보여줄 수 있는 #서울로 화면이다. (테스트용으로 후기사진을 업로드 하다보니…)

서버로 부터 사진의 id 값을 받아 뷰에 뿌려줄 때 Int.max와 같은 큰 값을 받아 갱신되는 사진들로 부터 최신 사진을 받을 수 있도록 네트워킹 구현을 했다.

1
2
3
4
5
6
7
8
var id = Int.max

override func viewWillAppear(_ animated: Bool) {
let model = ProofShotModel(self)
model.proofshot(id: id)

ProofCollecView.reloadData()
}

서울로 정보 (이벤트, 코스, 프로그램)

현재 서울로7017에서 진행하고 있는 이벤드들과 코스 설명 및 프로그램 신청페이지를 웹과 연동해주는 화면이다.(저작권 관련 문제로 이 부분을 웹페이지 연동으로 대체했다.)

3개의 SegmentedControl을 커스텀해여 디자인했고, 이벤트탭과 도시관광코스탭은 Tableview로 구성하였다. 특히 이벤트 탭은 아래와 같은 section 메소드로 분할 구분 하였다.

1
2
3
4
5
6
7
8
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if (0 == section){
return "진행중인 이벤트"
}
else{
return "지난 이벤트"
}
}

각각의 해당 버튼을 아래와 같은 URL 링크로 연결해주었다.

1
2
3
4
5
@IBAction func whithsimin_Btn(_ sender: UIButton) {
if let url = URL(string: "http://seoullo7017.seoul.go.kr/SSF/H/ENJ/010/05010.do") {
UIApplication.shared.openURL(url)
}
}

마이페이지

내가 쓴 후기와, 인증샷, 한줄 메시지 설정,프로필 사진 변경, 로그아웃이 가능한 마이페이지 화면이다.

‘나의 서울로’와 ‘내가 쓴 후기’는 SegmentedControl을 커스텀해 디자인하였고, ‘나의 서울로’는 Collectionview를 이용해 내가 올린 인증샷들을 한번에 볼수 있도록 하였다. ‘내가 쓴 후기’는 Tableview를 이용하였다.

한줄 메시지 편집은 view를 따로 만들어 CenterConstraintX를 임의 조정하는 방식(350 -> 0)을 사용해 커스텀 팝업을 구현했다.

커스텀 팝업 레이아웃 코드이다.

1
2
3
4
5
6
popupView.layer.cornerRadius = 10
popupView.layer.masksToBounds = true
popupView.layer.shadowColor = UIColor.darkGray.cgColor
popupView.layer.shadowRadius = 20
popupView.layer.shadowOpacity = 1.0
popupView.layer.shadowOffset = CGSize(width: 0, height: 0)

커스텀 팝업 클릭시 CenterConstraintX 변경 코드이다.

1
2
3
4
5
6
7
8
9
10
@IBAction func introduceChange(_ sender: Any) {
introChangeTxt.text = ""
currentTxtLength.text = ""
centerPopupConstraint.constant = 0
UIView.animate(withDuration: 0.2, animations: {
self.view.layoutIfNeeded()
self.backgroundBtn.isHidden = false
})

}

커스텀 팝업 ‘취소’, ‘완료’시 CenterConstraintX 변경 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func completeChange() {
let text = introChangeTxt.text
let model = MypageModel(self)
model.introduceChange(token: gsno(myToken), introduce: gsno(text))
centerPopupConstraint.constant = 350
UIView.animate(withDuration: 0.1, animations: {
self.view.layoutIfNeeded()
self.backgroundBtn.isHidden = true
})
}

@IBAction func closePopup(_ sender: Any) {
centerPopupConstraint.constant = 350
UIView.animate(withDuration: 0.1, animations: {
self.view.layoutIfNeeded()
self.backgroundBtn.isHidden = true
})
}

프로필 사진 변경은 extension 시킴으로써, imagepickerview가 모달식으로 뜨게 구현하였고, Alert창에서의 버튼 이벤트 클로저를 활용해 기본이미지 또는 사진첩 선택 이벤트를 구현하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@IBAction func profileChange(_ sender: Any) {
let alert = UIAlertController(title: "프로필 사진 변경", message: nil, preferredStyle: .actionSheet)
let pickOnGallery = UIAlertAction(title: "앨범에서 사진 선택", style: .default){
(_) in
self.present(self.imagePicker, animated: true, completion: nil)
}
let changeDefault = UIAlertAction(title: "기본이미지로 변경", style: .default){
(_) in
let defaultImg = UIImage(named: "mypage_icon_profile")
self.profileImageView.image = defaultImg
let model = MypageModel(self)
let imageData = UIImageJPEGRepresentation(defaultImg!, 0.5)
model.profileChange(token: self.gsno(self.myToken), imageData: imageData)
}
let cancelAction = UIAlertAction(title: "취소", style: .cancel)
alert.addAction(pickOnGallery)
alert.addAction(changeDefault)
alert.addAction(cancelAction)
present(alert, animated: true)
}