- ๋น์ทํ ์ ๋๋ฉ์ด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ ๊น ์๊ฐ ํ์ง๋ง ์๋์๋ฆฌ์ ๊ตฌ์กฐ๋ฅผ ๋ ์๊ณ ์ถ์ด์ ๋์
- ๋งจ ์ฒ์ ๊ตฌ์กฐ๋ถํฐ ์๊ฐ์ ํ๊ณ ํ
์คํธ๋ฅผ ๋จผ์ ํด๋ด
- CollectionView๋ฅผ Vertical Scroll๋ก ๋ง๋ค๊ณ Cell ์ ํ๋์์๋, ์ ์ด ํผ์ณ์ง๋ ๊ตฌ์กฐ๋ก ์๊ฐ
- ์ ์ ๋๋ ์๋ ์ ๋๋ฉ์ด์ ๊ณผ, ์คํ ์ดํฐ์ค๋ฐ ํ๋ ๋ฑ ์์ดํฐ ํ๋ฉด์ ๊ฝ์ฐจ๊ฒ ๊ตฌํ ์๋ฃ
- ๊ทธ๋ฌ๋! DetailView๋ ๊ธด ์คํฌ๋กค์ด ํ์ํด์ ์ด๋ฌํ ๊ตฌ์กฐ๋ก ๊ตฌํ์ ๋ถ๊ฐ๋ฅ
- ๊ตฌ๊ธ๋ง ๋ฐ ๋ค๋ฅธ ์ฝ๋๋ฅผ ์ฐธ์กฐ
- ํค๋๋ทฐ์ ๋ฐ๋๋ทฐ๋ก(์ค๋ช ํ๊ธฐ ํธํ๊ฒ) xib๋ฅผ ๋๋๊ณ , ์ ์ ๋๋ ์๋ ํค๋๋ทฐ๊ฐ ์ฌ๋ผ๊ฐ๋ฉด์ ๋ฐ๋๋ทฐ๋ฅผ ์ ๋๋ฉ์ด์ ์ ์ผ๋ก ๋ฐ๊ฟ์น๊ธฐ ํด์ฃผ๋ ๊ตฌ์กฐ๋ก ๊ตฌํ
- ๋ค๋น๊ฒ์ด์ ์ปจํธ๋กค๋ฌ์ push ์ ๋๋ฉ์ด์ ์ ์ปค์คํ ํด์ ์ ์ฉ, ์ด ๋ถ๋ถ์ ์์
extension RecommendViewController: Animatable {
var containerView: UIView? {
return self.recommendCollectionView
}
var childView: UIView? {
return self.selectedCell
}
}
์ด๋ฐ์์ผ๋ก ์ปจํ ์ด๋๋ทฐ๊ฐ ํค๋๋ทฐ , childView๋ ์คํฌ๋กค๋ ๋ค์ ๋ฉ์ธ๋ทฐ์ปจ์ ์๋ฏธ ํฉ๋๋ค.
-
๋ค์๊ณผ ๊ฐ์ด ์ ์ ๋ณด์ฌ์ง๋ ๋ทฐ๊ฐ, ์ด๋ก์๋ทฐ(์ปจํ ์ด๋๋ทฐ)๊ฐ ๋ณด์ฌ์ง๊ณ ์ ๋๋ฉ์ด์ ์ผ๋ก ์คํฌ๋กค๋๋ ๋ถ๋ถ์ด ๋ณด์ฌ์ง๋ ํ์
-
DetailView๋ ViewController๋ก ๋ง๋ค์ด์ง, ์ ๋๋ฉ์ด์ ์ extension์ ์ด์ฉํด ๋ค์๊ณผ ๊ฐ์ด ๊ตฌํ
extension DetailViewController: Animatable {
var containerView: UIView? {
return self.view
}
var childView: UIView? {
return self.commonView
}
func presentingView(
sizeAnimator: UIViewPropertyAnimator,
positionAnimator: UIViewPropertyAnimator,
fromFrame: CGRect,
toFrame: CGRect
) {
self.heightConstraint.constant = fromFrame.height
// Show the close button
self.closeButton.alpha = 1
self.asCard(true)
self.view.layoutIfNeeded()
let safeAreaTop = self.view.window?.safeAreaInsets.top ?? .zero
print("safeAreaTop ๊ฐ: \(safeAreaTop)")
self.commonView.topConstraintValue = safeAreaTop + 16
// Animate the common view to a height of 500 points
self.heightConstraint.constant = 405
sizeAnimator.addAnimations {
self.view.layoutIfNeeded()
}
// Animate the view to not look like a card
positionAnimator.addAnimations {
self.asCard(false)
}
}
func dismissingView(
sizeAnimator: UIViewPropertyAnimator,
positionAnimator: UIViewPropertyAnimator,
fromFrame: CGRect,
toFrame: CGRect
) {
self.topConstraint.isActive = true
if scrollView.contentOffset.y > commonView.frame.height {
self.topConstraint.constant = -commonView.frame.height
self.view.layoutIfNeeded()
// Still want to animate the common view getting pinned to the top of the view
self.topConstraint.constant = 0
}
self.commonView.topConstraintValue = 16
self.heightConstraint.constant = toFrame.height
sizeAnimator.addAnimations {
self.closeButton.alpha = 0
self.view.layoutIfNeeded()
}
// ์นด๋์ฒ๋ผ ๋ณด์ฌ์ง๋ ์ ๋๋ฉ์ด์
positionAnimator.addAnimations {
self.asCard(true)
}
}
}
- ํค๋๋ทฐ์ ํํค์ดํธ๋ฅผ ์ด์ฉํด ์กฐ์
- ๋ฆฌ์ปค๋ฉ๋๋ทฐ์์ ์๋จ, ์ค๋จ, ํ๋จ ์ ์ ์ ํ์ ๊ฐ๊ธฐ ๋ค๋ฅธ ํค๋์ ์ ๋๋ฉ์ด์ ์ด ์ ์ฉ
protocol Animatable {
var containerView: UIView? { get }
var childView: UIView? { get }
func presentingView(
sizeAnimator: UIViewPropertyAnimator,
positionAnimator: UIViewPropertyAnimator,
fromFrame: CGRect,
toFrame: CGRect
)
func dismissingView(
sizeAnimator: UIViewPropertyAnimator,
positionAnimator: UIViewPropertyAnimator,
fromFrame: CGRect,
toFrame: CGRect
)
}
/// Default implementations
extension Animatable {
func presentingView(
sizeAnimator: UIViewPropertyAnimator,
positionAnimator: UIViewPropertyAnimator,
fromFrame: CGRect,
toFrame: CGRect
) {}
func dismissingView(
sizeAnimator: UIViewPropertyAnimator,
positionAnimator: UIViewPropertyAnimator,
fromFrame: CGRect,
toFrame: CGRect
) {}
}
- Animatable ํ๋กํ ์ฝ์ ์ด์ฉํด ์ ๋๋ฉ์ดํ ๊ตฌํ
- ํค๋๋ทฐ์ ์คํฌ๋กค๋ทฐ๊ฐ ํ๋ฉด ์ ์ฒด์ ๊ฝ์ฐจ๊ฒ ๋๋ ์ ๋๋ฉ์ด์ ์ ์ฝ๋ ์ฐธ์กฐ ๋ฐ ๊ณต๋ถ๋ก ๊ตฌํ
- extension์ ์ด์ฉํด CustomTransitionAnimation ๊ตฌํ
- NavigationContorller์ Push๊ฐ ํด๋น ์ปค์คํ ์ ๋๋ฉ์ด์ ์ผ๋ก ๊ตฌํ ๋๊ฒ ๊ตฌํ
extension CustomTransitionAnimation {
// Custom push animations
internal func presentTransition(_ transitionContext: UIViewControllerContextTransitioning) {
print("presentTransition ์ง์
")
let container = transitionContext.containerView
// ===========================================================
// Step 1: Get the views we are animating
// ===========================================================
// Views we are animating FROM
guard
let fromVC = transitionContext.viewController(forKey: .from) as? Animatable,
let fromContainer = fromVC.containerView,
let fromChild = fromVC.childView
else {
return
}
// Views we are animating TO
guard
let toVC = transitionContext.viewController(forKey: .to) as? Animatable,
let toView = transitionContext.view(forKey: .to)
else {
return
}
// Preserve the original frame of the toView
let originalFrame = toView.frame
container.addSubview(toView)
// ===========================================================
// Step 2: Determine start and end points for animation
// ===========================================================
// Get the coordinates of the view inside the container
let originFrame = CGRect(
origin: fromContainer.convert(fromChild.frame.origin, to: container),
size: fromChild.frame.size
)
let destinationFrame = toView.frame
toView.frame = originFrame
toView.layoutIfNeeded()
fromChild.isHidden = true
// ===========================================================
// Step 3: Perform the animation
// ===========================================================
let yDiff = destinationFrame.origin.y - originFrame.origin.y
let xDiff = destinationFrame.origin.x - originFrame.origin.x
// For the duration of the animation, we are moving the frame. Therefore we have a separate animator
// to just control the Y positioning of the views. We will also use this animator to determine when
// all of our animations are done.
// Animate the card's vertical position
let positionAnimator = UIViewPropertyAnimator(duration: self.positioningDuration - 0.1, dampingRatio: 0.7) //base dampingRatio 0.7
positionAnimator.addAnimations {
// Move the view in the Y direction
toView.transform = CGAffineTransform(translationX: 0, y: yDiff)
}
// Animate the card's size
let sizeAnimator = UIViewPropertyAnimator(duration: self.resizingDuration - 0.1, curve: .easeInOut)
sizeAnimator.addAnimations {
// Animate the size of the Card View
toView.frame.size = destinationFrame.size
toView.layoutIfNeeded()
// Move the view in the X direction. We concatenate here because we do not want to overwrite our
// previous transformation
toView.transform = toView.transform.concatenating(CGAffineTransform(translationX: xDiff, y: 0))
}
// Call the animation delegate
toVC.presentingView(
sizeAnimator: sizeAnimator,
positionAnimator: positionAnimator,
fromFrame: originFrame,
toFrame: destinationFrame
)
// Animation completion.
let completionHandler: (UIViewAnimatingPosition) -> Void = { _ in
toView.transform = .identity
toView.frame = originalFrame
toView.layoutIfNeeded()
fromChild.isHidden = false
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
// Put the completion handler on the longest lasting animator
if (self.positioningDuration > self.resizingDuration) {
positionAnimator.addCompletion(completionHandler)
} else {
sizeAnimator.addCompletion(completionHandler)
}
// Kick off the two animations
positionAnimator.startAnimation()
sizeAnimator.startAnimation()
}
let marker = NMFMarker()
let bookstoreLatitude:Double = Double( self.detailBookStoreModel[0].latitude)
let bookstoreLongitude:Double = Double(self.detailBookStoreModel[0].longitude)
marker.position = NMGLatLng(lat: bookstoreLatitude, lng: bookstoreLongitude)
- ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ก ๋ง์ปค๋ฅผ ์ง๋์ ํํ ๊ฐ๋ฅ
- ๊ทธ๋ฌ๋ ์ค์ ์ฑ์์ ์ง๋๋ฅผ ๋ณด๋ฉด, ๋ณด์ด์ง ์์...Why? ์ง๋์ ์นด๋ฉ๋ผ ์ด๋ ๋ํ ๋ง์ปค๋ก ์ด๋์์ผ์ผํจ
let cameraUpdate = NMFCameraUpdate(scrollTo: NMGLatLng(lat: bookstoreLatitude, lng: bookstoreLongitude))
cameraUpdate.reason = 3
cameraUpdate.animation = .fly
cameraUpdate.animationDuration = 2
detailNaverMapView.mapType = .basic
detailNaverMapView.minZoomLevel = 5.0
detailNaverMapView.maxZoomLevel = 18.0
detailNaverMapView.zoomLevel = 15.0
detailNaverMapView.moveCamera(cameraUpdate, completion: { (isCancelled) in
if isCancelled {
print("์นด๋ฉ๋ผ ์ด๋ ์ทจ์")
} else {
print("์นด๋ฉ๋ผ ์ด๋ ์ฑ๊ณต")
}
})
marker.mapView = detailNaverMapView
-
๋ค์๊ณผ ๊ฐ์ด ์ง๋์ ํ๋ ๋ ๋ฒจ ๋ฐ ์นด๋ฉ๋ผ๋ฅผ ์ด๋ ์์ผ์ฃผ๋ฉด ์ํ๋ ๊ณณ์ ์๋ ๊ฒฝ๋๊ฐ์ ๋ง์ปค๋ฅผ ์ง๋๋ทฐ๋ก ํํ ๊ฐ๋ฅ
-
๋ํ ๋ค์์ ๋ง์ปค ํฐ์น ์ด๋ฒคํธ๋ฅผ ์ด์ฉํด ๋ค์ด๋ฒ์ง๋ ์ฑ์ผ๋ก ์ด๋๊ฐ๋ฅ
marker.touchHandler = { (overlay) in
print("๋ง์ปค ํด๋ฆญ๋จ")
self.goToNaverMap()
return true
}
- ์ฑ์ ์คํค๋งURL์ ์ด์ฉํด ํด๋น ์ฑ์ผ๋ก ์ด๋ ๊ฐ๋ฅ, ๋ค์ด๋ฒ ์ง๋ ์ฑ์ด ์์์ ์ฑ์คํ ์ด ๋ค์ด๋ฒ์ง๋๋ก ์ด๋
func goToNaverMap(){
let appStoreURL = URL(string: "http://itunes.apple.com/app/id311867728?mt=8")!
let latitude: Double = Double(self.detailBookStoreModel[0].latitude)
let longtitude: Double = Double(self.detailBookStoreModel[0].longitude)
if let detailMapURL = URL(string: "nmap://place?lat=\(latitude)&lng=\(longtitude)&name=Cozy%ea%b0%80%20%ec%b6%94%ec%b2%9c%ed%95%98%eb%8a%94%20%ec%84%9c%ec%a0%90&gamsung.Cozy=Cozy"), UIApplication.shared.canOpenURL(detailMapURL)
{ // ์ ํจํ URL์ธ์ง ๊ฒ์ฌํฉ๋๋ค.
if #available(iOS 10.0, *) { //iOS 10.0๋ถํฐ URL๋ฅผ ์คํํ๋ ๋ฐฉ๋ฒ์ด ๋ณ๊ฒฝ ๋์์ต๋๋ค.
UIApplication.shared.open(detailMapURL, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(appStoreURL)
}
}
}
- ์ค์ ์์ดํฐ์์ ์๋์ ๊ตฌํ์ด ์๋์ด์ง
- ๋ค์ด๋ฒ์ง๋ ์ฑ์ ๊ฐ๋ค๊ฐ ๋ค์, Cozy์ฑ์ผ๋ก ๋์์ฌ์ ํญ๋ฐ๊ฐ ๋ค์ ์์ฑ๋๋ ๋ฌธ์ ๋ฐ์
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.willEnterForegroundNotification, object: nil)
- ์ฑ๋๋ฆฌ๊ฒ์ดํธ ๋ฐฑ๊ทธ๋ผ์ด๋ ํธ์ถ์ ๋ค์ ํ๋ฒ ํญ๋ฐ๋ฅผ ์ฌ๋ผ์ง๊ฒ ํด์ฃผ์ด ๋ฌธ์ ํด๊ฒฐ
- ๋ค๋ฅธ ์ฑ์ ๊ฐ๋ค๊ฐ ๋ค์ ๋์์ฌ์, ํธ์ถ๋ฌธ์ ๋ก ์ธํด ํญ๋ฐ ํ๋ ์ด ํ๋ฆฌ๋ ๊ฒ์ผ๋ก ์ถ์
var index: Int = 0
self.myDetailReviewModel.forEach { element in
let eachLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.text = self.myDetailReviewModel[index].content
index += 1
return label
}()
let size = eachLabel.sizeThatFits(CGSize(width: 100, height: 100))
print(size.height/10)
self.eachCellHeight.append(320 + (size.height/10))
}
- ์์์ ๋ ์ด๋ธ์ ํ ์คํธ๋ฅผ ๋ฃ์ด์, ํค์ดํธ ์ฌ์ด์ฆ๋ฅผ ๋๋ต์ ์ผ๋ก ๊ตฌํจ
- ํค์ดํธ๊ฐ์ด ๋๋ฌด ํฌ๊ฒ ์กํ์, /10์ผ๋ก ๋น์จ๊ฐ์ ์กฐ์ ์ ์ํ๋ ์ ๋์ ์ฌ์ด์ฆ ๊ตฌํ ๊ฐ๋ฅ
- ์ ์ ์คํ ๋ ์ด์์๋ ๋ ์ด๋ธ์ด ๊ฐ๋ณ์ ์ผ๋ก ๋ณํ ์ ์๊ฒ ์ก์๋์ด์ผํจ
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
- ์คํ ๋ฉํฑ์ ์ ์ด์ฉํด ํค์ดํธ ๊ฐ์ ์กฐ์ ํ๋ ๋ฐฉ์๋ ์์
-
์ ๋๋ฉ์ด์ ๊ณผ, ํธ์ถ๋ฑ์ ๋ฐ๊ฟ ๋ณด์์ผ๋ ์์ธ์ด ๋ฑํ ์กํ์ง ์์
-
11pro, xs, promax ๊ณ์ด์ ์๋๋, ํ๋ฒํผ์ด ์๋ ์์ดํฐ8, SE2, 8+์์๋ ํ๋ ์์ ํค์ดํธ ์๋๋ฑ ๊ณ์ฐ์ด ๋ง๊ฒ ๋๋, ์ ๋๋ฉ์ด์ ๊ณผ isHidden ๋ํ ์๋ฌ ๋ฐ์
-
๊ธฐ๊ธฐ๋ณ๋ก ๋ถ๊ธฐ์ฒ๋ฆฌ๋ก ํด๊ฒฐ, ํ๋ก์ ํธ๊ฐ ๋๋ ํ ์กฐ๊ธ ๋ ์์ธ์ ๋ถ์!
switch deviceHeight {
case 667.0: //iphone 6, 6s, 7, 8 => 4.7 inch
self.tabBarController?.tabBar.isHidden = false
case 812.0: //iphone X, XS => 5.8 inch
self.setTabBarHidden(false)
default:
self.setTabBarHidden(false)
}