Skip to content

Commit 102aaca

Browse files
authored
Added option to launch Lyft in Safari without leaving the app (#4)
Created the concept of "deep link behavior" to specify between launching Lyft's mobile web experience and launching the native Lyft app
1 parent 9ea144e commit 102aaca

File tree

7 files changed

+150
-38
lines changed

7 files changed

+150
-38
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
All notable changes to this project will be documented in this file.
33
`LyftSDK` adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [1.0.4](https://github.com/lyft/Lyft-iOS-sdk/releases/tag/1.0.4)
6+
7+
- Added option to launch Lyft in Safari without leaving the app
8+
59
## [1.0.3](https://github.com/lyft/Lyft-iOS-sdk/releases/tag/1.0.3)
610

711
- Create framework project that can be manually imported

Example/LyftSDK-Example/Base.lproj/Main.storyboard

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11201" systemVersion="15G31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="8V6-UW-VVv">
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11535.1" systemVersion="15G31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="8V6-UW-VVv">
3+
<device id="retina4_7" orientation="portrait">
4+
<adaptation id="fullscreen"/>
5+
</device>
36
<dependencies>
4-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11161"/>
7+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11523"/>
58
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
69
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
710
</dependencies>
811
<scenes>
912
<!--Navigation Controller-->
1013
<scene sceneID="wqU-fF-VFp">
1114
<objects>
12-
<navigationController id="8V6-UW-VVv" sceneMemberID="viewController">
15+
<navigationController modalPresentationStyle="formSheet" id="8V6-UW-VVv" sceneMemberID="viewController">
1316
<navigationBar key="navigationBar" contentMode="scaleToFill" id="T6F-HV-AMh">
1417
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
1518
<autoresizingMask key="autoresizingMask"/>
@@ -35,12 +38,14 @@
3538
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
3639
<subviews>
3740
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="AcN-f4-hus" customClass="LyftButton" customModule="LyftSDK">
41+
<rect key="frame" x="57.5" y="308.5" width="260" height="50"/>
3842
<constraints>
3943
<constraint firstAttribute="height" constant="50" id="25p-6X-fLe"/>
4044
<constraint firstAttribute="width" constant="260" id="e3y-dY-yp5"/>
4145
</constraints>
4246
</view>
4347
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uid-4u-WuC">
48+
<rect key="frame" x="0.0" y="607" width="375" height="60"/>
4449
<color key="backgroundColor" red="0.74901962280273438" green="0.78039216995239258" blue="0.85098040103912354" alpha="1" colorSpace="calibratedRGB"/>
4550
<constraints>
4651
<constraint firstAttribute="height" constant="60" id="ATa-dO-spJ"/>
@@ -81,30 +86,35 @@
8186
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
8287
<prototypes>
8388
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="ride_type" id="u4z-ZE-eh8" customClass="RideTypeCell" customModule="LyftSDK_Example" customModuleProvider="target">
84-
<rect key="frame" x="0.0" y="92" width="375" height="72"/>
89+
<rect key="frame" x="0.0" y="28" width="375" height="72"/>
8590
<autoresizingMask key="autoresizingMask"/>
8691
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="u4z-ZE-eh8" id="2JG-eh-uOP">
87-
<frame key="frameInset" width="375" height="71"/>
92+
<rect key="frame" x="0.0" y="0.0" width="375" height="71.5"/>
8893
<autoresizingMask key="autoresizingMask"/>
8994
<subviews>
9095
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="chL-yz-6Sq">
96+
<rect key="frame" x="8" y="0.0" width="71.5" height="71.5"/>
9197
<constraints>
9298
<constraint firstAttribute="width" secondItem="chL-yz-6Sq" secondAttribute="height" multiplier="1:1" id="iYF-6S-OnX"/>
9399
</constraints>
94100
</imageView>
95101
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ffY-HY-Y13">
102+
<rect key="frame" x="87.5" y="16" width="271.5" height="39.5"/>
96103
<subviews>
97104
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="&lt;Seats 4&gt;" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rhd-Y6-4wh">
105+
<rect key="frame" x="0.0" y="22.5" width="196.5" height="17"/>
98106
<fontDescription key="fontDescription" type="system" pointSize="14"/>
99107
<nil key="textColor"/>
100108
<nil key="highlightedColor"/>
101109
</label>
102110
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="&lt;Name&gt;" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KWc-wC-cDc">
111+
<rect key="frame" x="0.0" y="0.0" width="196.5" height="21.5"/>
103112
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
104113
<nil key="textColor"/>
105114
<nil key="highlightedColor"/>
106115
</label>
107116
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="749" text="&lt;2 min&gt;" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9NY-4G-gPC">
117+
<rect key="frame" x="204.5" y="9" width="67" height="21.5"/>
108118
<fontDescription key="fontDescription" type="system" pointSize="18"/>
109119
<nil key="textColor"/>
110120
<nil key="highlightedColor"/>

LyftSDK.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
FB7CA7731DD1256800303FEB /* LyftButtonNone.xib in Resources */ = {isa = PBXBuildFile; fileRef = FB7CA7541DD1256800303FEB /* LyftButtonNone.xib */; };
3939
FB7CA7741DD1256800303FEB /* LyftButtonRideTypeETA.xib in Resources */ = {isa = PBXBuildFile; fileRef = FB7CA7551DD1256800303FEB /* LyftButtonRideTypeETA.xib */; };
4040
FB7CA7751DD1256800303FEB /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB7CA7561DD1256800303FEB /* UIColor+Extension.swift */; };
41+
FBE45A251DEE585300A701D8 /* Safari.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBE45A241DEE585300A701D8 /* Safari.swift */; };
4142
/* End PBXBuildFile section */
4243

4344
/* Begin PBXContainerItemProxy section */
@@ -84,6 +85,7 @@
8485
FB7CA7541DD1256800303FEB /* LyftButtonNone.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LyftButtonNone.xib; sourceTree = "<group>"; };
8586
FB7CA7551DD1256800303FEB /* LyftButtonRideTypeETA.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LyftButtonRideTypeETA.xib; sourceTree = "<group>"; };
8687
FB7CA7561DD1256800303FEB /* UIColor+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = "<group>"; };
88+
FBE45A241DEE585300A701D8 /* Safari.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Safari.swift; sourceTree = "<group>"; };
8789
/* End PBXFileReference section */
8890

8991
/* Begin PBXFrameworksBuildPhase section */
@@ -175,6 +177,7 @@
175177
FB7CA7471DD1256800303FEB /* LyftUI */ = {
176178
isa = PBXGroup;
177179
children = (
180+
FBE45A241DEE585300A701D8 /* Safari.swift */,
178181
FB7CA7481DD1256800303FEB /* Asset.swift */,
179182
FB7CA7491DD1256800303FEB /* Button.swift */,
180183
FB7CA74A1DD1256800303FEB /* I18N.swift */,
@@ -335,6 +338,7 @@
335338
FB7CA7591DD1256800303FEB /* Bundle+Extension.swift in Sources */,
336339
FB7CA7651DD1256800303FEB /* NearbyDriver.swift in Sources */,
337340
FB7CA75A1DD1256800303FEB /* HTTPClient.swift in Sources */,
341+
FBE45A251DEE585300A701D8 /* Safari.swift in Sources */,
338342
FB7CA7631DD1256800303FEB /* ETA.swift in Sources */,
339343
FB7CA76E1DD1256800303FEB /* LyftDeepLink.swift in Sources */,
340344
FB7CA7681DD1256800303FEB /* RideType.swift in Sources */,

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,16 @@ lyftButton.configure(pickup: pickup, destination: destination)
8787
#### Ride types
8888
Lyft is growing very quickly and is currently available in [these cities](https://www.lyft.com/cities). Please keep in mind that some ride types (such as Lyft Line) are not yet available in all Lyft cities. If you set the ride type of the button and it happens to be unavailable, the button will default to the Lyft Classic ride type. You can utilize the `LyftAPI.rideTypes(at:completion:)` wrapper to get a list of the available ride types in an area.
8989

90+
#### Deep link behavior
91+
When a user taps on the Lyft Button, the default behavior is to deep link into the native Lyft app with the configuration information you have provided.
92+
93+
However, if you would like to create a ride request on Lyft using Lyft's mobile web experience, you can specify the button's `deepLinkBehavior` as follows:
94+
```swift
95+
lyftButton.deepLinkBehavior = .web
96+
```
97+
98+
This is preferable if you do not want the user to leave your app when making a ride request. Also, it does not require the user has Lyft installed.
99+
90100
#### Button styles
91101
To specify the button style, use `enum LyftButtonStyle`
92102
```swift
@@ -117,6 +127,8 @@ let destination = CLLocationCoordinate2D(latitude: 37.7794703, longitude: -122.4
117127
LyftDeepLink.requestRide(kind: .Standard, from: pickup, to: destination)
118128
```
119129

130+
Note that you can specify a LyftDeepLinkBehavior in this request, to decide between deep linking to the native lyft app or launching Lyft's mobile web experience within your own app.
131+
120132
## Support
121133

122134
If you're looking for help configuring or using the SDK, or if you have general questions related to our APIs, the Lyft Developer Platform team provides support through our [forum](https://developer.lyft.com/discuss) as well as on Stack Overflow (using the `lyft-api` tag)

Sources/LyftUI/LyftButton.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ public class LyftButton: UIView {
5555
didSet { self.setupStyle() }
5656
}
5757

58+
/// The deep link behavior for this button
59+
public var deepLinkBehavior: LyftDeepLinkBehavior = .native
60+
5861
/// Initializes a LyftButton with its preferred size
5962
convenience public init() {
6063
self.init(frame: CGRect(origin: CGPoint.zero, size: LyftButtonPreferredSize))
@@ -91,8 +94,10 @@ public class LyftButton: UIView {
9194
public func configure(rideKind: RideKind = .Standard, pickup: CLLocationCoordinate2D? = nil,
9295
destination: CLLocationCoordinate2D? = nil)
9396
{
94-
self.pressUpAction = {
95-
LyftDeepLink.requestRide(kind: rideKind, from: pickup, to: destination)
97+
self.pressUpAction = { [weak self] in
98+
if let behavior = self?.deepLinkBehavior {
99+
LyftDeepLink.requestRide(using: behavior, kind: rideKind, from: pickup, to: destination)
100+
}
96101
}
97102

98103
guard let pickup = pickup else {

Sources/LyftUI/LyftDeepLink.swift

Lines changed: 75 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,57 +2,90 @@ import Foundation
22
import CoreLocation
33
import UIKit
44

5+
/// Designates the kind of deeplinking to perform
6+
///
7+
/// - native: Launches the native Lyft app if available
8+
/// - web: Launches a safari view controller without leaving the app, enabling ride request function
9+
public enum LyftDeepLinkBehavior {
10+
case native
11+
case web
12+
13+
fileprivate var baseUrl: URL? {
14+
switch self {
15+
case .native:
16+
return URL(string: "lyft://")
17+
18+
case .web:
19+
return URL(string: "https://ride.lyft.com/")
20+
}
21+
}
22+
}
23+
524
/// Collection of deep links into the main Lyft application
625
public struct LyftDeepLink {
26+
727
/// Prepares to request a ride with the given parameters
828
///
9-
/// - parameter kind: The kind of ride to create a request for
10-
/// - parameter pickup: The pickup position of the ride
11-
/// - parameter destination: The destination position of the ride
12-
/// - parameter couponCode A coupon code to be applied to the user
13-
public static func requestRide(kind: RideKind,
29+
/// - parameter behavior: The deep linking mode to use for this deep link
30+
/// - parameter kind: The kind of ride to create a request for
31+
/// - parameter pickup: The pickup position of the ride
32+
/// - parameter destination: The destination position of the ride
33+
/// - parameter couponCode: A coupon code to be applied to the user
34+
public static func requestRide(using behavior: LyftDeepLinkBehavior = .native, kind: RideKind = .Standard,
1435
from pickup: CLLocationCoordinate2D? = nil,
1536
to destination: CLLocationCoordinate2D? = nil,
1637
couponCode: String? = nil)
1738
{
18-
var parameters = ["id": kind.rawValue]
39+
let action: String
40+
var parameters = [String: Any]()
41+
parameters["partner"] = LyftConfiguration.developer?.clientId
42+
parameters["credits"] = couponCode
1943

20-
if let pickup = pickup {
21-
parameters["pickup[latitude]"] = String(pickup.latitude)
22-
parameters["pickup[longitude]"] = String(pickup.longitude)
23-
}
44+
switch behavior {
45+
case .native:
46+
action = "ridetype"
47+
parameters["id"] = kind.rawValue
48+
parameters["pickup[latitude]"] = pickup.map { $0.latitude }
49+
parameters["pickup[longitude]"] = pickup.map { $0.longitude }
50+
parameters["destination[latitude]"] = destination.map { $0.latitude }
51+
parameters["destination[longitude]"] = destination.map { $0.longitude }
2452

25-
if let destination = destination {
26-
parameters["destination[latitude]"] = String(destination.latitude)
27-
parameters["destination[longitude]"] = String(destination.longitude)
53+
case .web:
54+
action = "request"
55+
parameters["ride_type"] = kind.rawValue
56+
parameters["pickup"] = pickup.map { "@\($0.latitude),\($0.longitude)" }
57+
parameters["destination"] = destination.map { "@\($0.latitude),\($0.longitude)" }
2858
}
2959

30-
parameters["partner"] = LyftConfiguration.developer?.clientId
31-
parameters["credits"] = couponCode
32-
33-
self.launch(action: "ridetype", parameters: parameters)
60+
self.launch(using: behavior, action: action, parameters: parameters)
3461
}
3562

36-
private static func launch(action: String, parameters: [String: String]?) {
37-
var requestURLComponents = URLComponents()
38-
requestURLComponents.scheme = "lyft"
39-
requestURLComponents.host = action
40-
requestURLComponents.queryItems = parameters?.map { key, value in
41-
URLQueryItem(name: key, value: value)
63+
private static func launch(using behavior: LyftDeepLinkBehavior, action: String,
64+
parameters: [String: Any])
65+
{
66+
guard let originalUrl = behavior.baseUrl.flatMap({ URL(string: action, relativeTo: $0) }) else {
67+
return
4268
}
4369

44-
guard let url = requestURLComponents.url else {
70+
let request = lyftURLEncodedInURL(request: URLRequest(url: originalUrl), parameters: parameters).0
71+
guard let url = request.url else {
4572
return
4673
}
4774

48-
if #available(iOS 10.0, *) {
49-
UIApplication.shared.open(url, options: [:]) { success in
50-
if !success {
51-
self.launchAppStore()
75+
switch behavior {
76+
case .native:
77+
if #available(iOS 10.0, *) {
78+
UIApplication.shared.open(url, options: [:]) { success in
79+
if !success {
80+
self.launchAppStore()
81+
}
82+
}
83+
} else {
84+
UIApplication.shared.openURL(url)
5285
}
53-
}
54-
} else {
55-
UIApplication.shared.openURL(url)
86+
87+
case .web:
88+
Safari.openURL(url, from: UIApplication.shared.topViewController)
5689
}
5790
}
5891

@@ -68,3 +101,14 @@ public struct LyftDeepLink {
68101
}
69102
}
70103
}
104+
105+
fileprivate extension UIApplication {
106+
fileprivate var topViewController: UIViewController? {
107+
var topController = self.keyWindow?.rootViewController
108+
while let viewController = topController?.presentedViewController {
109+
topController = viewController
110+
}
111+
112+
return topController
113+
}
114+
}

0 commit comments

Comments
 (0)