আমি নীচে আমার উত্তর উপর ভিত্তি করে একটি গ্রন্থাগার মুক্তি ।
এটি শর্টকাট অ্যাপ্লিকেশন ওভারলে নকল করে। বিশদ জন্য এই নিবন্ধটি দেখুন ।
লাইব্রেরির প্রধান উপাদান OverlayContainerViewController
। এটি এমন একটি অঞ্চলকে সংজ্ঞায়িত করে যেখানে ভিউ কন্ট্রোলারটিকে উপরে এবং নীচে টেনে আনা যায়, এর নীচে থাকা সামগ্রী লুকিয়ে রাখতে বা প্রকাশ করতে পারে।
let contentController = MapsViewController()
let overlayController = SearchViewController()
let containerController = OverlayContainerViewController()
containerController.delegate = self
containerController.viewControllers = [
contentController,
overlayController
]
window?.rootViewController = containerController
বাস্তবায়ন OverlayContainerViewControllerDelegate
আকাঙ্ক্ষিত notches সংখ্যা নির্দিষ্ট করতে:
enum OverlayNotch: Int, CaseIterable {
case minimum, medium, maximum
}
func numberOfNotches(in containerViewController: OverlayContainerViewController) -> Int {
return OverlayNotch.allCases.count
}
func overlayContainerViewController(_ containerViewController: OverlayContainerViewController,
heightForNotchAt index: Int,
availableSpace: CGFloat) -> CGFloat {
switch OverlayNotch.allCases[index] {
case .maximum:
return availableSpace * 3 / 4
case .medium:
return availableSpace / 2
case .minimum:
return availableSpace * 1 / 4
}
}
পূর্ববর্তী উত্তর
আমি মনে করি একটি উল্লেখযোগ্য পয়েন্ট রয়েছে যা প্রস্তাবিত সমাধানগুলিতে চিকিত্সা করা হয় না: স্ক্রোল এবং অনুবাদের মধ্যে রূপান্তর।
মানচিত্রে, যেমন আপনি লক্ষ্য করেছেন, টেবিলভিউ পৌঁছে contentOffset.y == 0
গেলে নীচের শীটটি স্লাইড হয় বা নীচে যায়।
বিষয়টিটি মুশকিল কারণ আমাদের প্যান অঙ্গভঙ্গিটি অনুবাদটি শুরু করার পরে আমরা কেবল স্ক্রোলটি সক্ষম / অক্ষম করতে পারি না। কোনও নতুন স্পর্শ শুরু না হওয়া পর্যন্ত এটি স্ক্রোলটি থামিয়ে দেবে। এখানে প্রস্তাবিত বেশিরভাগ সমাধানের ক্ষেত্রে এটিই।
এই গতিটি বাস্তবায়নের জন্য আমার চেষ্টা এখানে।
শুরুর পয়েন্ট: মানচিত্র অ্যাপ্লিকেশন
আমাদের তদন্ত শুরু করতে, আসুন মানচিত্রের চিত্রক্রমটি ভিজ্যুয়ালাইজ করা যাক (একটি সিমুলেটরে মানচিত্র শুরু করুন এবং>>> এক্সকোড 9 এ > নির্বাচন Debug
করুন )।Attach to process by PID or Name
Maps
এটি গতিটি কীভাবে কাজ করে তা বলে না, তবে এটির যুক্তি বুঝতে আমাকে সহায়তা করেছিল। আপনি এলএলডিবি এবং ভিউ হায়ারার্কি ডিবাগার দিয়ে খেলতে পারেন।
আমাদের দেখুন নিয়ামক স্ট্যাক
আসুন মানচিত্র ভিউ কন্ট্রোলার আর্কিটেকচারের একটি প্রাথমিক সংস্করণ তৈরি করি।
আমরা একটি BackgroundViewController
(আমাদের মানচিত্রের দৃশ্য) দিয়ে শুরু করি :
class BackgroundViewController: UIViewController {
override func loadView() {
view = MKMapView()
}
}
আমরা একটি উত্সর্গীকৃত টেবিল ভিউ রাখি UIViewController
:
class OverlayViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
lazy var tableView = UITableView()
override func loadView() {
view = tableView
tableView.dataSource = self
tableView.delegate = self
}
[...]
}
এখন, ওভারলে এম্বেড করতে এবং এর অনুবাদ পরিচালনা করতে আমাদের একটি ভিসি দরকার। সমস্যাটিকে সহজ করার জন্য, আমরা বিবেচনা করি যে এটি ওভারলেটিকে একটি স্থিতিশীল বিন্দু OverlayPosition.maximum
থেকে অন্য স্থানে অনুবাদ করতে পারে OverlayPosition.minimum
।
আপাতত অবস্থানের পরিবর্তনটি অ্যানিমেটেড করার জন্য এটির জন্য একটি জনসাধারণ পদ্ধতি রয়েছে এবং এর স্বচ্ছ দৃষ্টিভঙ্গি রয়েছে:
enum OverlayPosition {
case maximum, minimum
}
class OverlayContainerViewController: UIViewController {
let overlayViewController: OverlayViewController
var translatedViewHeightContraint = ...
override func loadView() {
view = UIView()
}
func moveOverlay(to position: OverlayPosition) {
[...]
}
}
সবগুলি এম্বেড করার জন্য অবশেষে আমাদের একটি ভিউকন্ট্রোলার দরকার:
class StackViewController: UIViewController {
private var viewControllers: [UIViewController]
override func viewDidLoad() {
super.viewDidLoad()
viewControllers.forEach { gz_addChild($0, in: view) }
}
}
আমাদের অ্যাপডেলিগেটে, আমাদের প্রারম্ভিক ক্রমটি দেখে মনে হচ্ছে:
let overlay = OverlayViewController()
let containerViewController = OverlayContainerViewController(overlayViewController: overlay)
let backgroundViewController = BackgroundViewController()
window?.rootViewController = StackViewController(viewControllers: [backgroundViewController, containerViewController])
ওভারলে অনুবাদ পিছনে অসুবিধা
এখন, কীভাবে আমাদের ওভারলে অনুবাদ করবেন?
প্রস্তাবিত বেশিরভাগ সমাধানগুলিতে একটি উত্সর্গীকৃত প্যান অঙ্গভঙ্গি সনাক্তকারী ব্যবহার করে তবে আমাদের ইতিমধ্যে একটি রয়েছে: টেবিলের দৃশ্যের প্যান অঙ্গভঙ্গি। তদ্ব্যতীত, আমাদের স্ক্রোল এবং অনুবাদটি সিঙ্ক্রোনাইজ করা এবং আমাদের UIScrollViewDelegate
সমস্ত ইভেন্টের দরকার আছে!
একটি নিষ্পাপ বাস্তবায়ন দ্বিতীয় প্যান অঙ্গভঙ্গি ব্যবহার করবে এবং contentOffset
অনুবাদটি আসার পরে সারণী দর্শনটির পুনরায় সেট করার চেষ্টা করবে :
func panGestureAction(_ recognizer: UIPanGestureRecognizer) {
if isTranslating {
tableView.contentOffset = .zero
}
}
কিন্তু এটা কাজ করে না। contentOffset
নিজস্ব প্যান অঙ্গভঙ্গি সনাক্তকারী ক্রিয়াটি ট্রিগার করা হয় বা এর ডিসপ্লেলিঙ্ক কলব্যাক কল করার সময় টেবিলভিউ এটি আপডেট করে । সফলভাবে ওভাররাইড করার পরে আমাদের শনাক্তকারী ঠিক তখনই ট্রিগার করতে পারে এমন কোনও সম্ভাবনা নেই contentOffset
। আমাদের একমাত্র সুযোগটি হ'ল লেআউট পর্বের অংশ নেওয়া ( layoutSubviews
স্ক্রোল ভিউয়ের প্রতিটি ফ্রেমে স্ক্রোল ভিউ কলগুলি ওভাররাইড করে ) বা didScroll
প্রতিবার contentOffset
সংশোধিত হওয়ার সময় ডেলিগ্রেটের পদ্ধতিতে সাড়া দেওয়া । এর এক চেষ্টা করুন।
অনুবাদ বাস্তবায়ন
OverlayVC
স্ক্রোলভিউয়ের ইভেন্টগুলি আমাদের অনুবাদ হ্যান্ডলারের কাছে প্রেরণে আমরা একটি প্রতিনিধি যুক্ত করি OverlayContainerViewController
:
protocol OverlayViewControllerDelegate: class {
func scrollViewDidScroll(_ scrollView: UIScrollView)
func scrollViewDidStopScrolling(_ scrollView: UIScrollView)
}
class OverlayViewController: UIViewController {
[...]
func scrollViewDidScroll(_ scrollView: UIScrollView) {
delegate?.scrollViewDidScroll(scrollView)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
delegate?.scrollViewDidStopScrolling(scrollView)
}
}
আমাদের পাত্রে, আমরা একটি এনাম ব্যবহার করে অনুবাদটি ট্র্যাক করি:
enum OverlayInFlightPosition {
case minimum
case maximum
case progressing
}
বর্তমান অবস্থানের গণনা দেখে মনে হচ্ছে:
private var overlayInFlightPosition: OverlayInFlightPosition {
let height = translatedViewHeightContraint.constant
if height == maximumHeight {
return .maximum
} else if height == minimumHeight {
return .minimum
} else {
return .progressing
}
}
অনুবাদটি পরিচালনা করতে আমাদের 3 টি পদ্ধতি দরকার:
প্রথমটি আমাদের জানান যে আমাদের অনুবাদ শুরু করতে হবে কিনা।
private func shouldTranslateView(following scrollView: UIScrollView) -> Bool {
guard scrollView.isTracking else { return false }
let offset = scrollView.contentOffset.y
switch overlayInFlightPosition {
case .maximum:
return offset < 0
case .minimum:
return offset > 0
case .progressing:
return true
}
}
দ্বিতীয়টি অনুবাদ সম্পাদন করে। এটি translation(in:)
স্ক্রোলভিউর প্যান অঙ্গভঙ্গির পদ্ধতিটি ব্যবহার করে ।
private func translateView(following scrollView: UIScrollView) {
scrollView.contentOffset = .zero
let translation = translatedViewTargetHeight - scrollView.panGestureRecognizer.translation(in: view).y
translatedViewHeightContraint.constant = max(
Constant.minimumHeight,
min(translation, Constant.maximumHeight)
)
}
তৃতীয়টি অনুবাদকের শেষটি অ্যানিমেট করে যখন ব্যবহারকারী তার আঙুলটি প্রকাশ করে। আমরা বেগ এবং দৃশ্যের বর্তমান অবস্থান ব্যবহার করে অবস্থান গণনা করি।
private func animateTranslationEnd() {
let position: OverlayPosition = // ... calculation based on the current overlay position & velocity
moveOverlay(to: position)
}
আমাদের ওভারলে-র প্রতিনিধি বাস্তবায়ন কেবল এ রকম দেখায়:
class OverlayContainerViewController: UIViewController {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard shouldTranslateView(following: scrollView) else { return }
translateView(following: scrollView)
}
func scrollViewDidStopScrolling(_ scrollView: UIScrollView) {
// prevent scroll animation when the translation animation ends
scrollView.isEnabled = false
scrollView.isEnabled = true
animateTranslationEnd()
}
}
চূড়ান্ত সমস্যা: ওভারলে কন্টেইনারটির ছোঁয়া প্রেরণ
অনুবাদ এখন বেশ দক্ষ। তবে এখনও একটি চূড়ান্ত সমস্যা রয়েছে: স্পর্শগুলি আমাদের পটভূমিতে দেখার জন্য সরবরাহ করা হয় না। এগুলি সমস্ত ওভারলে কন্টেইনারের দৃষ্টিতে আটকে রয়েছে। আমরা সেট isUserInteractionEnabled
করতে পারি না false
কারণ এটি আমাদের টেবিল ভিউতে মিথস্ক্রিয়াটিকে অক্ষম করে। সমাধান হ'ল ম্যাপস অ্যাপে ব্যাপকভাবে ব্যবহৃত হয় PassThroughView
:
class PassThroughView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
if view == self {
return nil
}
return view
}
}
এটি প্রতিক্রিয়াশীল চেইন থেকে নিজেকে সরিয়ে দেয়।
ইন OverlayContainerViewController
:
override func loadView() {
view = PassThroughView()
}
ফলাফল
ফলাফল এখানে:
আপনি কোডটি এখানে খুঁজে পেতে পারেন ।
আপনি যদি কোনও বাগ দেখতে পান তবে আমাকে জানান! মনে রাখবেন যে আপনার বাস্তবায়ন অবশ্যই দ্বিতীয় প্যান অঙ্গভঙ্গি ব্যবহার করতে পারে, বিশেষত যদি আপনি নিজের ওভারলেতে শিরোনাম যোগ করেন।
আপডেট 23/08/18
আমরা প্রতিস্থাপন করতে পারেন scrollViewDidEndDragging
সঙ্গে
willEndScrollingWithVelocity
বদলে enabling
/ disabling
স্ক্রল যখন ব্যবহারকারী প্রান্ত টেনে:
func scrollView(_ scrollView: UIScrollView,
willEndScrollingWithVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>) {
switch overlayInFlightPosition {
case .maximum:
break
case .minimum, .progressing:
targetContentOffset.pointee = .zero
}
animateTranslationEnd(following: scrollView)
}
গতি প্রবাহকে আরও উন্নত করতে আমরা অ্যানিমেট করার সময় একটি বসন্ত অ্যানিমেশন ব্যবহার করতে পারি এবং ব্যবহারকারীর মিথস্ক্রিয়াকে অনুমতি দিতে পারি:
func moveOverlay(to position: OverlayPosition,
duration: TimeInterval,
velocity: CGPoint) {
overlayPosition = position
translatedViewHeightContraint.constant = translatedViewTargetHeight
UIView.animate(
withDuration: duration,
delay: 0,
usingSpringWithDamping: velocity.y == 0 ? 1 : 0.6,
initialSpringVelocity: abs(velocity.y),
options: [.allowUserInteraction],
animations: {
self.view.layoutIfNeeded()
}, completion: nil)
}