Skip to content
This repository was archived by the owner on Aug 30, 2023. It is now read-only.

Commit b60e1b0

Browse files
Add sliding menu as an example
#20
1 parent b9ea0c3 commit b60e1b0

File tree

4 files changed

+234
-0
lines changed

4 files changed

+234
-0
lines changed

examples/MenuInteractiveExample.swift

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
Copyright 2017-present The Material Motion Authors. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import UIKit
18+
import Transitioning
19+
20+
// This example demonstrates the minimal path to building a custom transition using the Material
21+
// Motion Transitioning APIs in Swift. The essential steps have been documented below.
22+
23+
class MenuInteractiveExampleViewController: ExampleViewController {
24+
25+
func didTap() {
26+
let modalViewController = ModalInteractiveViewController()
27+
28+
// The transition controller is an associated object on all UIViewController instances that
29+
// allows you to customize the way the view controller is presented. The primary API on the
30+
// controller that you'll make use of is the `transition` property. Setting this property will
31+
// dictate how the view controller is presented. For this example we've built a custom
32+
// FadeTransition, so we'll make use of that now:
33+
modalViewController.transitionController.transition = MenuTransition()
34+
modalViewController.transitionController.interactiveTransition = MenuInteractiveTransition()
35+
36+
// Note that once we assign the transition object to the view controller, the transition will
37+
// govern all subsequent presentations and dismissals of that view controller instance. If we
38+
// want to use a different transition (e.g. to use an edge-swipe-to-dismiss transition) then we
39+
// can simply change the transition object before initiating the transition.
40+
41+
present(modalViewController, animated: true)
42+
}
43+
44+
override func viewDidLoad() {
45+
super.viewDidLoad()
46+
47+
let label = UILabel(frame: view.bounds)
48+
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
49+
label.textColor = .white
50+
label.textAlignment = .center
51+
label.text = "Swipe from left edge to start the transition"
52+
view.addSubview(label)
53+
54+
let tap = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(edgePanGesture))
55+
tap.edges = .left
56+
view.addGestureRecognizer(tap)
57+
58+
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
59+
}
60+
61+
override func exampleInformation() -> ExampleInfo {
62+
return .init(title: type(of: self).catalogBreadcrumbs().last!,
63+
instructions: "Tap to present a modal transition.")
64+
}
65+
66+
var percentage = CGFloat(0.01)
67+
func edgePanGesture(_ sender: UIScreenEdgePanGestureRecognizer) {
68+
let translation = sender.location(in: sender.view?.superview)
69+
switch sender.state {
70+
case .began:
71+
didTap()
72+
case .changed:
73+
percentage = translation.x / ((sender.view!.frame.width)/2)
74+
percentage = min(percentage, 0.99)
75+
interactiveTransitionContext?.updatePercent(percentage)
76+
case .ended:
77+
if percentage > 0.8 {
78+
interactiveTransitionContext?.finishInteractiveTransition()
79+
} else {
80+
interactiveTransitionContext?.cancelInteractiveTransition()
81+
}
82+
interactiveTransitionContext = nil
83+
percentage = CGFloat(0.01)
84+
default:
85+
break
86+
}
87+
}
88+
}
89+
90+
// Transitions must be NSObject types that conform to the Transition protocol.
91+
private final class MenuTransition: NSObject, Transition {
92+
93+
// The sole method we're expected to implement, start is invoked each time the view controller is
94+
// presented or dismissed.
95+
func start(with context: TransitionContext) {
96+
let fromVC = context.backViewController
97+
let toVC = context.foreViewController
98+
let containerView = context.containerView
99+
100+
if(context.direction == .forward) {
101+
containerView.insertSubview(toVC.view, aboveSubview: fromVC.view)
102+
toVC.view.frame.origin.x = -1 * toVC.view.frame.width
103+
UIView.animate(
104+
withDuration: context.duration,
105+
delay: 0,
106+
options: .curveLinear,
107+
animations: {
108+
toVC.view.frame.origin.x = -1 * (toVC.view.frame.width / 2)
109+
},
110+
completion: { _ in
111+
let deadlineTime = DispatchTime.now() + .milliseconds(100)
112+
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
113+
context.transitionDidEnd()
114+
}
115+
}
116+
)
117+
if let snapshot = fromVC.view.snapshotView(afterScreenUpdates: false) {
118+
snapshot.isUserInteractionEnabled = false
119+
containerView.insertSubview(snapshot, belowSubview: toVC.view)
120+
snapshot.tag = 2000
121+
}
122+
} else {
123+
UIView.animate(
124+
withDuration: context.duration,
125+
delay: 0,
126+
options: .curveLinear,
127+
animations: {
128+
toVC.view.frame.origin.x = -1 * toVC.view.frame.width
129+
},
130+
completion: { _ in
131+
132+
let deadlineTime = DispatchTime.now() + .milliseconds(100)
133+
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
134+
context.transitionDidEnd()
135+
}
136+
137+
if(context.wasCancelled == false) {
138+
containerView.viewWithTag(2000)?.removeFromSuperview()
139+
containerView.insertSubview(toVC.view, aboveSubview: fromVC.view)
140+
}
141+
}
142+
)
143+
}
144+
}
145+
}
146+
147+
final class MenuInteractiveTransition: NSObject, InteractiveTransition {
148+
func isInteractive(_ context: TransitionContext) -> Bool {
149+
return true
150+
}
151+
152+
func start(withInteractiveContext context: InteractiveTransitionContext) {
153+
context.transitionContext.sourceViewController!.interactiveTransitionContext = context
154+
context.transitionContext.foreViewController.interactiveTransitionContext = context
155+
}
156+
}

examples/apps/Catalog/TableOfContents.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ extension FadeExampleViewController {
2020
class func catalogBreadcrumbs() -> [String] { return ["1. Fade transition"] }
2121
}
2222

23+
extension MenuInteractiveExampleViewController {
24+
class func catalogBreadcrumbs() -> [String] { return ["2. Menu transition (interactive)"] }
25+
}
26+
2327
extension CustomPresentationExampleViewController {
2428
class func catalogBreadcrumbs() -> [String] { return ["2. Custom presentation transitions"] }
2529
}

examples/apps/Catalog/TransitionsCatalog.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
076819261EEF9F4200B572D7 /* ModalInteractiveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 076819251EEF9F4200B572D7 /* ModalInteractiveViewController.swift */; };
11+
076819281EEF9F4E00B572D7 /* MenuInteractiveExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 076819271EEF9F4E00B572D7 /* MenuInteractiveExample.swift */; };
1012
6629151E1ED5E0E0002B9A5D /* CustomPresentationExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6629151D1ED5E0E0002B9A5D /* CustomPresentationExample.swift */; };
1113
662915201ED5E137002B9A5D /* ModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6629151F1ED5E137002B9A5D /* ModalViewController.swift */; };
1214
662915231ED64A10002B9A5D /* TransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662915221ED64A10002B9A5D /* TransitionTests.swift */; };
@@ -46,6 +48,8 @@
4648
/* End PBXContainerItemProxy section */
4749

4850
/* Begin PBXFileReference section */
51+
076819251EEF9F4200B572D7 /* ModalInteractiveViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModalInteractiveViewController.swift; sourceTree = "<group>"; };
52+
076819271EEF9F4E00B572D7 /* MenuInteractiveExample.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuInteractiveExample.swift; sourceTree = "<group>"; };
4953
0C2327F961D4F16DEBF0EEB8 /* Pods-UnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.debug.xcconfig"; path = "../../../Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.debug.xcconfig"; sourceTree = "<group>"; };
5054
2408A4B72C0BA93CC963452F /* Pods_UnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_UnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5155
3734DFFD1C84494E48784617 /* Pods-TransitionsCatalog.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TransitionsCatalog.release.xcconfig"; path = "../../../Pods/Target Support Files/Pods-TransitionsCatalog/Pods-TransitionsCatalog.release.xcconfig"; sourceTree = "<group>"; };
@@ -169,6 +173,7 @@
169173
6629151D1ED5E0E0002B9A5D /* CustomPresentationExample.swift */,
170174
66BBC7731ED729A70015CB9B /* FadeExample.h */,
171175
66BBC7741ED729A70015CB9B /* FadeExample.m */,
176+
076819271EEF9F4E00B572D7 /* MenuInteractiveExample.swift */,
172177
);
173178
name = examples;
174179
path = ../..;
@@ -220,6 +225,7 @@
220225
66BBC76B1ED4C8790015CB9B /* HexColor.swift */,
221226
66BBC76C1ED4C8790015CB9B /* Layout.swift */,
222227
6629151F1ED5E137002B9A5D /* ModalViewController.swift */,
228+
076819251EEF9F4200B572D7 /* ModalInteractiveViewController.swift */,
223229
);
224230
name = supplemental;
225231
path = ../../supplemental;
@@ -474,6 +480,8 @@
474480
66BBC7751ED729A80015CB9B /* FadeExample.m in Sources */,
475481
66BBC76D1ED4C8790015CB9B /* ExampleViewController.swift in Sources */,
476482
667A3F541DEE273000CB3A99 /* TableOfContents.swift in Sources */,
483+
076819261EEF9F4200B572D7 /* ModalInteractiveViewController.swift in Sources */,
484+
076819281EEF9F4E00B572D7 /* MenuInteractiveExample.swift in Sources */,
477485
66BBC7701ED4C8790015CB9B /* Layout.swift in Sources */,
478486
6629151E1ED5E0E0002B9A5D /* CustomPresentationExample.swift in Sources */,
479487
66BBC76E1ED4C8790015CB9B /* ExampleViews.swift in Sources */,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
Copyright 2017-present The Material Motion Authors. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import Foundation
18+
import UIKit
19+
20+
class ModalInteractiveViewController: ExampleViewController {
21+
22+
override func viewDidLoad() {
23+
super.viewDidLoad()
24+
25+
view.backgroundColor = .primaryColor
26+
27+
view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handleGesture)))
28+
29+
let label = UILabel(frame: view.bounds)
30+
label.numberOfLines = 0
31+
label.lineBreakMode = .byWordWrapping
32+
label.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In aliquam dolor eget orci condimentum, eu blandit metus dictum. Suspendisse vitae metus pellentesque, sagittis massa vel, sodales velit. Aliquam placerat nibh et posuere interdum. Etiam fermentum purus vel turpis lobortis auctor. Curabitur auctor maximus purus, ac iaculis mi. In ac hendrerit sapien, eget porttitor risus. Integer placerat cursus viverra. Proin mollis nulla vitae nisi posuere, eu rutrum mauris condimentum. Nullam in faucibus nulla, non tincidunt lectus. Maecenas mollis massa purus, in viverra elit molestie eu. Nunc volutpat magna eget mi vestibulum pharetra. Suspendisse nulla ligula, laoreet non ante quis, vehicula facilisis libero. Morbi faucibus, sapien a convallis sodales, leo quam scelerisque leo, ut tincidunt diam velit laoreet nulla. Proin at quam vel nibh varius ultrices porta id diam. Pellentesque pretium consequat neque volutpat tristique. Sed placerat a purus ut molestie. Nullam laoreet venenatis urna non pulvinar. Proin a vestibulum nulla, eu placerat est. Morbi molestie aliquam justo, ut aliquet neque tristique consectetur. In hac habitasse platea dictumst. Fusce vehicula justo in euismod elementum. Ut vel malesuada est. Aliquam mattis, ex vel viverra eleifend, mauris nibh faucibus nibh, in fringilla sem purus vitae elit. Donec sed dapibus orci, ut vulputate sapien. Integer eu magna efficitur est pellentesque tempor. Sed ac imperdiet ex. Maecenas congue quis lacus vel dictum. Phasellus dictum mi at sollicitudin euismod. Mauris laoreet, eros vitae euismod commodo, libero ligula pretium massa, in scelerisque eros dui eu metus. Fusce elementum mauris velit, eu tempor nulla congue ut. In at tellus id quam feugiat semper eget ut felis. Nulla quis varius quam. Nullam tincidunt laoreet risus, ut aliquet nisl gravida id. Nulla iaculis mauris velit, vitae feugiat nunc scelerisque ac. Vivamus eget ligula porta, pulvinar ex vitae, sollicitudin erat. Maecenas semper ornare suscipit. Ut et neque condimentum lectus pulvinar maximus in sit amet odio. Aliquam congue purus erat, eu rutrum risus placerat a."
33+
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
34+
view.addSubview(label)
35+
}
36+
37+
override var preferredStatusBarStyle: UIStatusBarStyle {
38+
return .lightContent
39+
}
40+
41+
var percentage = CGFloat(0.01)
42+
func handleGesture(_ sender: UIPanGestureRecognizer) {
43+
let translation = sender.location(in: sender.view?.superview)
44+
switch sender.state {
45+
case .began:
46+
dismiss(animated: true, completion: nil)
47+
case .changed:
48+
percentage = 1-(translation.x / (sender.view!.frame.width/2))
49+
print(percentage)
50+
percentage = min(percentage, 0.99)
51+
print(translation.x)
52+
53+
interactiveTransitionContext?.updatePercent(percentage)
54+
case .ended:
55+
if percentage > 0.8 {
56+
interactiveTransitionContext?.finishInteractiveTransition()
57+
} else {
58+
interactiveTransitionContext?.cancelInteractiveTransition()
59+
}
60+
interactiveTransitionContext = nil
61+
percentage = CGFloat(0.01)
62+
default:
63+
break
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)