├── .gitignore ├── .travis.yml ├── Cupid.podspec ├── CupidDemo ├── Cupid │ ├── Cupid.h │ ├── Cupid │ │ ├── Activity │ │ │ └── ShareActivity.swift │ │ ├── Content.swift │ │ ├── Cupid.swift │ │ ├── LICENSE │ │ ├── Network │ │ │ ├── SimpleNetworking.swift │ │ │ ├── SimpleWebView.swift │ │ │ ├── URLHandler.swift │ │ │ └── Utils.swift │ │ └── Service Provider │ │ │ ├── Cupid+Protocol.swift │ │ │ ├── PasteboardServiceProvider.swift │ │ │ ├── PocketServiceProvider.swift │ │ │ ├── QQServiceProvider.swift │ │ │ ├── WeChatServiceProvider.swift │ │ │ └── WeiboServiceProvider.swift │ └── Info.plist ├── CupidDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Cupid.xcscheme ├── CupidDemo.xcworkspace │ └── contents.xcworkspacedata ├── CupidDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Untitled-1.png │ │ │ ├── Untitled-2.png │ │ │ ├── Untitled.png │ │ │ ├── Untitled@2x-1.png │ │ │ ├── Untitled@2x-2.png │ │ │ ├── Untitled@2x-3.png │ │ │ ├── Untitled@2x-4.png │ │ │ ├── Untitled@2x-5.png │ │ │ ├── Untitled@2x-6.png │ │ │ ├── Untitled@2x-7.png │ │ │ ├── Untitled@2x.png │ │ │ ├── Untitled@3x-1.png │ │ │ ├── Untitled@3x-2.png │ │ │ ├── Untitled@3x-3.png │ │ │ └── Untitled@3x.png │ │ ├── Contents.json │ │ ├── Cupid.imageset │ │ │ ├── Contents.json │ │ │ └── Cupid.png │ │ ├── sns_share_alipay.imageset │ │ │ ├── Contents.json │ │ │ ├── sns_share_alipay@2x.png │ │ │ └── sns_share_alipay@3x.png │ │ ├── sns_share_copy.imageset │ │ │ ├── Contents.json │ │ │ ├── news-icon-share-link@2x.png │ │ │ └── news-icon-share-link@3x.png │ │ ├── sns_share_email.imageset │ │ │ ├── Contents.json │ │ │ └── mail@1x-58x58.pdf │ │ ├── sns_share_friends.imageset │ │ │ ├── Contents.json │ │ │ └── moment@1x-58x58.pdf │ │ ├── sns_share_message.imageset │ │ │ ├── Contents.json │ │ │ └── message@1x-58x58.pdf │ │ ├── sns_share_qq.imageset │ │ │ ├── Contents.json │ │ │ └── QQ@1x-58x58.pdf │ │ ├── sns_share_weibo.imageset │ │ │ ├── Contents.json │ │ │ └── weibo@1x-58x58.pdf │ │ └── sns_share_weixin.imageset │ │ │ ├── Contents.json │ │ │ └── wechat@1x-58x58.pdf │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Bridging-Header.h │ ├── Info.plist │ ├── MainViewController.swift │ ├── Notification Manager │ │ └── NotificationManager.swift │ └── Share Manager │ │ ├── Service Provider │ │ └── AlipayServiceProvider.swift │ │ ├── ShareManager+Type.swift │ │ ├── ShareManager.swift │ │ └── ShareSDK │ │ └── AlipaySDK │ │ ├── APOpenAPI.h │ │ ├── APOpenAPIObject.h │ │ └── libAPOpenSdk.a ├── Podfile.lock ├── Pods │ ├── Manifest.lock │ ├── Pods.xcodeproj │ │ └── project.pbxproj │ ├── StatusBarNotificationCenter │ │ ├── LICENSE │ │ ├── Pod │ │ │ ├── Configuration │ │ │ │ ├── NotificationCenterConfiguration.swift │ │ │ │ └── NotificationLabelConfiguration.swift │ │ │ └── Notification Center │ │ │ │ ├── BaseScrollLabel.swift │ │ │ │ ├── BaseViewController.swift │ │ │ │ ├── BaseWindow.swift │ │ │ │ ├── StatusBarNotificationCenter+Logic.swift │ │ │ │ ├── StatusBarNotificationCenter+Type.swift │ │ │ │ └── StatusBarNotificationCenter.swift │ │ └── README.md │ └── Target Support Files │ │ ├── Pods-CupidDemo │ │ ├── Info.plist │ │ ├── Pods-CupidDemo-acknowledgements.markdown │ │ ├── Pods-CupidDemo-acknowledgements.plist │ │ ├── Pods-CupidDemo-dummy.m │ │ ├── Pods-CupidDemo-frameworks.sh │ │ ├── Pods-CupidDemo-resources.sh │ │ ├── Pods-CupidDemo-umbrella.h │ │ ├── Pods-CupidDemo.debug.xcconfig │ │ ├── Pods-CupidDemo.modulemap │ │ └── Pods-CupidDemo.release.xcconfig │ │ └── StatusBarNotificationCenter │ │ ├── Info.plist │ │ ├── StatusBarNotificationCenter-dummy.m │ │ ├── StatusBarNotificationCenter-prefix.pch │ │ ├── StatusBarNotificationCenter-umbrella.h │ │ ├── StatusBarNotificationCenter.modulemap │ │ └── StatusBarNotificationCenter.xcconfig └── podfile ├── LICENSE ├── README.md └── screenshots └── animated.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | # AppCode 26 | .idea/ 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift -------------------------------------------------------------------------------- /Cupid.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "Cupid" 4 | s.version = "1.0.7" 5 | s.summary = "Cupid is the ultimate Swift library to deal with third party share service for Chinese app." 6 | 7 | s.description = <<-DESC 8 | Cupid is highly inspired by `MonkeyKing`, but with different code structure and data types. And with much more extainablity. 9 | You can use it to post messages to QQ, WeChat, Weibo, Pocket, Pasteboard or do OAuth. With minimal code, you can even create your own share and OAuth service provider, such as Alipay! 10 | ![screenshot](screenshots/animated.gif) 11 | DESC 12 | 13 | s.homepage = "https://github.com/36Kr-Mobile/Cupid.git" 14 | 15 | s.license = { :type => "MIT", :file => "LICENSE" } 16 | 17 | s.authors = { "Shannon Wu" => "inatu@icloud.com" } 18 | 19 | s.ios.deployment_target = "8.0" 20 | 21 | s.source = { :git => "https://github.com/36Kr-Mobile/Cupid.git", :tag => s.version } 22 | s.source_files = "CupidDemo/Cupid/**/*.swift" 23 | s.requires_arc = true 24 | 25 | end 26 | -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid.h: -------------------------------------------------------------------------------- 1 | // 2 | // Cupid.h 3 | // Cupid 4 | // 5 | // Created by Shannon Wu on 12/2/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Cupid. 12 | FOUNDATION_EXPORT double CupidVersionNumber; 13 | 14 | //! Project version string for Cupid. 15 | FOUNDATION_EXPORT const unsigned char CupidVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Activity/ShareActivity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnyActivity.swift 3 | // Cupid 4 | // 5 | // Created by Shannon Wu on 15/9/11. 6 | // Copyright © 2015年 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// A template for UIActivity, check UIActivity documentation for more information. 12 | public class ShareActivity: UIActivity { 13 | 14 | /// The type of the activity 15 | public var type: String 16 | /// The title of the activity 17 | public var title: String 18 | /// The image of the activity 19 | public var image: UIImage 20 | 21 | /// The content payload of the activity, check the documentation of the service provider for more information 22 | public var content: Content 23 | /// The service provider of the activity 24 | public var serviceProvider: ShareServiceProvider 25 | /// Actions after completion 26 | public var completionHandler: ShareCompletionHandler? 27 | 28 | /// Init a new share activity 29 | /// 30 | /// - parameter type: type 31 | /// - parameter title: title 32 | /// - parameter image: image 33 | /// - parameter content: content 34 | /// - parameter serviceProvider: service provider 35 | /// - parameter completionHandler: completion handler 36 | public init(type: String, title: String, image: UIImage, content: Content, serviceProvider: ShareServiceProvider, completionHandler: ShareCompletionHandler? = nil) { 37 | 38 | self.type = type 39 | self.title = title 40 | self.image = image 41 | 42 | self.content = content 43 | self.serviceProvider = serviceProvider 44 | self.completionHandler = completionHandler 45 | 46 | super.init() 47 | } 48 | 49 | override public class func activityCategory() -> UIActivityCategory { 50 | return .Share 51 | } 52 | 53 | override public func activityType() -> String? { 54 | return type 55 | } 56 | 57 | override public func activityTitle() -> String? { 58 | return title 59 | } 60 | 61 | override public func activityImage() -> UIImage? { 62 | return image 63 | } 64 | 65 | override public func canPerformWithActivityItems(activityItems: [AnyObject]) -> Bool { 66 | return serviceProvider.canShareContent(content) 67 | } 68 | 69 | override public func performActivity() { 70 | do { 71 | try Cupid.shareContent(content, serviceProvider: serviceProvider, completionHandler: completionHandler) 72 | } 73 | catch _ { 74 | activityDidFinish(false) 75 | } 76 | activityDidFinish(true) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Content.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Content.swift 3 | // China 4 | // 5 | // Created by Shannon Wu on 11/29/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Content to for Cupid to share, you must check the documentation of the service provider to learn how to configure the content payload 12 | public struct Content { 13 | /// The media payload of the content 14 | public enum Media { 15 | /// URL media payload, it contains a rawvalue of the URL 16 | case URL(NSURL) 17 | /// Image media payload, it contains a rawvalue of UIImage object 18 | case Image(UIImage) 19 | /// Audio media payload, it contains a the audio URL and optionally the link URL 20 | case Audio(audioURL:NSURL, linkURL:NSURL?) 21 | /// Video media payload, it contains a rawvalue of the URL of the video 22 | case Video(NSURL) 23 | } 24 | 25 | /// Te title of the content 26 | public var title: String? { 27 | set { 28 | internalTitle = newValue 29 | } 30 | get { 31 | return internalTitle ?? "" 32 | } 33 | } 34 | var internalTitle: String? 35 | 36 | /// The description of the content 37 | public var description: String? { 38 | set { 39 | internalDescription = newValue 40 | } 41 | get { 42 | return internalDescription ?? "" 43 | } 44 | } 45 | var internalDescription: String? 46 | 47 | 48 | /// The thumbnail of the content 49 | public var thumbnail: UIImage? { 50 | set { 51 | internalThumbnail = newValue 52 | } 53 | get { 54 | return internalThumbnail?.imageForShare 55 | } 56 | } 57 | var internalThumbnail: UIImage? 58 | 59 | /// The media payload of the content 60 | public var media: Media? 61 | 62 | /// Init a new content instance, with designated title, description, thumbnail, media 63 | public init(title: String?, description: String?, thumbnail: UIImage?, media: Media?) { 64 | self.title = title 65 | self.description = description 66 | self.thumbnail = thumbnail 67 | self.media = media 68 | } 69 | 70 | /// Init a new content instance with all values set to nil. 71 | public init() {} 72 | } -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Cupid.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cupid.swift 3 | // Cupid 4 | // 5 | // Created by Shannon Wu on 15/9/11. 6 | // Copyright © 2015年 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// A central hub for share and OAuth 12 | public struct Cupid { 13 | static var serviceProvider: ShareServiceProvider? 14 | static var intenalNetworkingProvider: CupidNetworkingProvider? 15 | /// Networking provider for request, if you don't specify your own, default will be used. 16 | public static var networkingProvier: CupidNetworkingProvider { 17 | set { 18 | intenalNetworkingProvider = networkingProvier 19 | } 20 | 21 | get { 22 | if let networkingProvider = intenalNetworkingProvider { 23 | return networkingProvider 24 | } else { 25 | return SimpleNetworking.sharedInstance 26 | } 27 | } 28 | } 29 | 30 | /// Share content to service provider 31 | /// 32 | /// - parameter content: the content to share 33 | /// - parameter serviceProvider: the service provider 34 | /// - parameter completionHandler: actions after completion, default to nil 35 | /// 36 | /// - throws: errors may occur in share process 37 | public static func shareContent(content: Content, serviceProvider: ShareServiceProvider, completionHandler: ShareCompletionHandler? = nil) throws { 38 | self.serviceProvider = serviceProvider 39 | 40 | func completionHandlerCleaner(succeed: Bool) { 41 | completionHandler?(succeed: succeed) 42 | Cupid.serviceProvider = nil 43 | } 44 | 45 | do { 46 | try serviceProvider.shareContent(content, completionHandler: completionHandlerCleaner) 47 | } 48 | catch let error { 49 | Cupid.serviceProvider = nil 50 | throw error 51 | } 52 | } 53 | 54 | /// OAuth to service provider 55 | /// 56 | /// - parameter serviceProvider: the service provider used for OAuth 57 | /// - parameter completionHandler: actions after OAuth completion 58 | /// 59 | /// - throws: errors may occur in OAuth process 60 | public static func OAuth(serviceProvider: ShareServiceProvider, completionHandler: NetworkResponseHandler) throws { 61 | self.serviceProvider = serviceProvider 62 | 63 | func completionHandlerCleaner(dictionary: NSDictionary?, response: NSURLResponse?, error: NSError?) { 64 | completionHandler(dictionary, response, error) 65 | Cupid.serviceProvider = nil 66 | } 67 | 68 | do { 69 | try serviceProvider.OAuth(completionHandler) 70 | } 71 | catch let error { 72 | Cupid.serviceProvider = nil 73 | throw error 74 | } 75 | } 76 | 77 | /// Handle URL realted to Cupid, you must call this method to check the callbacks. 78 | /// 79 | /// - parameter URL: the URL of the callback 80 | /// 81 | /// - returns: nil if the URL is not for Cupid, true for success, false for fail 82 | public static func handleOpenURL(URL: NSURL) -> Bool? { 83 | if let serviceProvider = serviceProvider { 84 | return serviceProvider.handleOpenURL(URL) 85 | } 86 | else { 87 | return false 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 36Kr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Network/SimpleNetworking.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleNetworking.swift 3 | // Cupid 4 | // 5 | // Created by Limon on 15/9/25. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class SimpleNetworking: CupidNetworkingProvider { 12 | 13 | static let sharedInstance = SimpleNetworking() 14 | private let session = NSURLSession.sharedSession() 15 | 16 | func request(URL: NSURL, method: CupidNetworkingMethod, parameters: [String:AnyObject]? = nil, completionHandler: NetworkResponseHandler) { 17 | 18 | let mutableURLRequest = NSMutableURLRequest(URL: URL) 19 | mutableURLRequest.HTTPMethod = method.rawValue 20 | 21 | let request = encode(mutableURLRequest, parameters: parameters) 22 | 23 | let task = session.dataTaskWithRequest(request) { 24 | (data, response, error) -> Void in 25 | 26 | var JSON: NSDictionary? 27 | 28 | defer { 29 | completionHandler(JSON, response, error) 30 | } 31 | 32 | guard let validData = data, let JSONData = try? NSJSONSerialization.JSONObjectWithData(validData, options: .AllowFragments) as? NSDictionary else { 33 | print("JSON could not be serialized because input data was nil.") 34 | return 35 | } 36 | 37 | JSON = JSONData 38 | } 39 | 40 | task.resume() 41 | } 42 | 43 | func request(URLString: String, method: CupidNetworkingMethod, parameters: [String:AnyObject]? = nil, completionHandler: NetworkResponseHandler) { 44 | guard let URL = NSURL(string: URLString) else { 45 | print("URL init Error: URLString") 46 | return 47 | } 48 | 49 | Cupid.networkingProvier.request(URL, method: method, parameters: parameters, completionHandler: completionHandler) 50 | } 51 | 52 | func upload(URL: NSURL, parameters: [String:AnyObject], completionHandler: NetworkResponseHandler) { 53 | 54 | let tuple = urlRequestWithComponents(URL.absoluteString, parameters: parameters) 55 | 56 | guard let request = tuple.request, let data = tuple.data else { 57 | return 58 | } 59 | 60 | let uploadTask = session.uploadTaskWithRequest(request, fromData: data) { 61 | (data, response, error) -> Void in var JSON: NSDictionary? 62 | 63 | defer { 64 | completionHandler(JSON, response, error) 65 | } 66 | 67 | guard let validData = data, let JSONData = try? NSJSONSerialization.JSONObjectWithData(validData, options: .AllowFragments) as? NSDictionary else { 68 | print("JSON could not be serialized because input data was nil.") 69 | return 70 | } 71 | 72 | JSON = JSONData 73 | } 74 | 75 | uploadTask.resume() 76 | } 77 | 78 | private func encode(URLRequest: NSMutableURLRequest, parameters: [String:AnyObject]?) -> NSURLRequest { 79 | if parameters == nil { 80 | return URLRequest 81 | } 82 | 83 | var mutableURLRequest: NSMutableURLRequest! = URLRequest.mutableCopy() as! NSMutableURLRequest 84 | 85 | func query(parameters: [String:AnyObject]) -> String { 86 | var components: [(String, String)] = [] 87 | 88 | for key in Array(parameters.keys).sort(<) { 89 | let value: AnyObject! = parameters[key] 90 | components += queryComponents(key, value) 91 | } 92 | 93 | return (components.map { 94 | "\($0)=\($1)" 95 | } as [String]).joinWithSeparator("&") 96 | } 97 | 98 | let method = CupidNetworkingMethod(rawValue: mutableURLRequest.HTTPMethod)! 99 | 100 | switch method { 101 | case .GET: 102 | if let URLComponents = NSURLComponents(URL: mutableURLRequest.URL!, resolvingAgainstBaseURL: false) { 103 | URLComponents.percentEncodedQuery = (URLComponents.percentEncodedQuery != nil ? URLComponents.percentEncodedQuery! + "&" : "") + query(parameters!) 104 | mutableURLRequest.URL = URLComponents.URL 105 | } 106 | default: 107 | do { 108 | let options = NSJSONWritingOptions() 109 | let data = try NSJSONSerialization.dataWithJSONObject(parameters!, options: options) 110 | 111 | mutableURLRequest.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type") 112 | mutableURLRequest.setValue("application/json", forHTTPHeaderField: "X-Accept") 113 | mutableURLRequest.HTTPBody = data 114 | } 115 | catch { 116 | print("SimpleNetworking: HTTPBody Encode") 117 | } 118 | } 119 | 120 | func queryComponents(key: String, _ value: AnyObject) -> [(String, String)] { 121 | var components: [(String, String)] = [] 122 | if let dictionary = value as? [String:AnyObject] { 123 | for (nestedKey, value) in dictionary { 124 | components += queryComponents("\(key)[\(nestedKey)]", value) 125 | } 126 | } 127 | else if let array = value as? [AnyObject] { 128 | for value in array { 129 | components += queryComponents("\(key)[]", value) 130 | } 131 | } 132 | else { 133 | components.appendContentsOf([(escape(key), escape("\(value)"))]) 134 | } 135 | 136 | return components 137 | } 138 | 139 | func escape(string: String) -> String { 140 | let legalURLCharactersToBeEscaped: CFStringRef = ":/?&=;+!@#$()',*" 141 | return CFURLCreateStringByAddingPercentEscapes(nil, string, nil, legalURLCharactersToBeEscaped, CFStringBuiltInEncodings.UTF8.rawValue) as String 142 | } 143 | 144 | return mutableURLRequest 145 | } 146 | 147 | private func urlRequestWithComponents(URLString: String, parameters: [String:AnyObject]) -> (request:NSURLRequest?, data:NSData?) { 148 | 149 | guard let URL = NSURL(string: URLString) else { 150 | return (nil, nil) 151 | } 152 | 153 | // create url request to send 154 | let mutableURLRequest = NSMutableURLRequest(URL: URL) 155 | mutableURLRequest.HTTPMethod = CupidNetworkingMethod.POST.rawValue 156 | let boundaryConstant = "NET-POST-boundary-\(arc4random())-\(arc4random())" 157 | let contentType = "multipart/form-data;boundary=" + boundaryConstant 158 | mutableURLRequest.setValue(contentType, forHTTPHeaderField: "Content-Type") 159 | 160 | let uploadData = NSMutableData() 161 | 162 | // add parameters 163 | for (key, value) in parameters { 164 | 165 | guard let encodeBoundaryData = "\r\n--\(boundaryConstant)\r\n".dataUsingEncoding(NSUTF8StringEncoding) else { 166 | return (nil, nil) 167 | } 168 | 169 | uploadData.appendData(encodeBoundaryData) 170 | 171 | if let imageData = value as? NSData { 172 | 173 | let filename = arc4random() 174 | let filenameClause = "filename=\"\(filename)\"" 175 | let contentDispositionString = "Content-Disposition: form-data; name=\"\(key)\";\(filenameClause)\r\n" 176 | let contentDispositionData = contentDispositionString.dataUsingEncoding(NSUTF8StringEncoding) 177 | uploadData.appendData(contentDispositionData!) 178 | 179 | // append content type 180 | let contentTypeString = "Content-Type: image/JPEG\r\n\r\n" 181 | guard let contentTypeData = contentTypeString.dataUsingEncoding(NSUTF8StringEncoding) else { 182 | return (nil, nil) 183 | } 184 | uploadData.appendData(contentTypeData) 185 | uploadData.appendData(imageData) 186 | 187 | } 188 | else { 189 | 190 | guard let encodeDispositionData = "Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n\(value)".dataUsingEncoding(NSUTF8StringEncoding) else { 191 | return (nil, nil) 192 | } 193 | uploadData.appendData(encodeDispositionData) 194 | } 195 | } 196 | 197 | uploadData.appendData("\r\n--\(boundaryConstant)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!) 198 | 199 | return (encode(mutableURLRequest, parameters: nil), uploadData) 200 | } 201 | 202 | } 203 | -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Network/SimpleWebView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleWebView.swift 3 | // China 4 | // 5 | // Created by Shannon Wu on 11/29/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | 12 | class SimpleWebView: NSObject, WKNavigationDelegate { 13 | weak var shareServiceProvider: ShareServiceProvider? 14 | 15 | func addWebViewByURLString(URLString: String, flagCode: String? = nil) { 16 | 17 | guard let URL = NSURL(string: URLString) else { 18 | return 19 | } 20 | 21 | let webView = WKWebView() 22 | webView.navigationDelegate = self 23 | webView.frame = UIScreen.mainScreen().bounds 24 | webView.frame.origin.y = UIScreen.mainScreen().bounds.height 25 | 26 | webView.loadRequest(NSURLRequest(URL: URL)) 27 | webView.backgroundColor = UIColor(red: 247 / 255, green: 247 / 255, blue: 247 / 255, alpha: 1.0) 28 | webView.scrollView.frame.origin.y = 20 29 | webView.scrollView.backgroundColor = webView.backgroundColor 30 | 31 | let activityIndicatorView = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) 32 | activityIndicatorView.center = CGPoint(x: CGRectGetMidX(webView.bounds), y: CGRectGetMidY(webView.bounds) + 30) 33 | activityIndicatorView.activityIndicatorViewStyle = .Gray 34 | 35 | webView.scrollView.addSubview(activityIndicatorView) 36 | activityIndicatorView.startAnimating() 37 | 38 | UIApplication.sharedApplication().keyWindow?.addSubview(webView) 39 | UIView.animateWithDuration(0.32, delay: 0.0, options: .CurveEaseOut, animations: { 40 | webView.frame.origin.y = 0 41 | }, completion: nil) 42 | 43 | // FlagCode For Pocket 44 | guard let code = flagCode else { 45 | return 46 | } 47 | webView.layer.name = code 48 | } 49 | 50 | func webView(webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: NSError) { 51 | 52 | // Pocket OAuth 53 | if let errorString = error.userInfo["NSErrorFailingURLStringKey"] as? String where errorString.hasSuffix(":authorizationFinished") { 54 | var consumerKey = "" 55 | 56 | if let pocketServiceProvider = shareServiceProvider as? PocketServiceProvider { 57 | consumerKey = pocketServiceProvider.appID 58 | } 59 | 60 | activityIndicatorViewAction(webView, stop: true) 61 | webView.stopLoading() 62 | 63 | guard let code = webView.layer.name else { 64 | let error = NSError(domain: "Code is nil", code: -1, userInfo: nil) 65 | hideWebView(webView, tuples: (nil, nil, error)) 66 | return 67 | } 68 | 69 | let accessTokenAPI = "https://getpocket.com/v3/oauth/authorize" 70 | let parameters = ["consumer_key": consumerKey, "code": code] 71 | 72 | Cupid.networkingProvier.request(accessTokenAPI, method: .POST, parameters: parameters) { 73 | (dictionary, response, error) in dispatch_async(dispatch_get_main_queue()) { 74 | self.hideWebView(webView, tuples: (dictionary, response, error)) 75 | } 76 | } 77 | } 78 | } 79 | 80 | func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!) { 81 | 82 | for subview in webView.scrollView.subviews { 83 | if let activityIndicatorView = subview as? UIActivityIndicatorView { 84 | activityIndicatorView.stopAnimating() 85 | } 86 | } 87 | 88 | let HTML = "var button = document.createElement('a'); button.setAttribute('href', 'about:blank'); button.innerHTML = '关闭'; button.setAttribute('style', 'width: calc(100% - 40px); background-color: gray;display: inline-block;height: 40px;line-height: 40px;text-align: center;color: #777777;text-decoration: none;border-radius: 3px;background: linear-gradient(180deg, white, #f1f1f1);border: 1px solid #CACACA;box-shadow: 0 2px 3px #DEDEDE, inset 0 0 0 1px white;text-shadow: 0 2px 0 white;position: absolute;bottom: 0;margin: 20px 20px 40px 20px;font-size: 18px;'); document.body.appendChild(button); document.querySelector('aside.logins').style.display = 'none';" 89 | 90 | webView.evaluateJavaScript(HTML, completionHandler: nil) 91 | } 92 | 93 | func webView(webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { 94 | 95 | guard let URL = webView.URL else { 96 | webView.stopLoading() 97 | return 98 | } 99 | 100 | // Close Button 101 | if URL.absoluteString.containsString("about:blank") { 102 | let error = NSError(domain: "User Cancelled", code: -1, userInfo: nil) 103 | hideWebView(webView, tuples: (nil, nil, error)) 104 | } 105 | 106 | // QQ Web OAuth 107 | guard URL.absoluteString.containsString("&access_token=") else { 108 | return 109 | } 110 | 111 | guard let fragment = URL.fragment?.characters.dropFirst(), newURL = NSURL(string: "limon.top/?\(String(fragment))") else { 112 | return 113 | } 114 | 115 | let components = NSURLComponents(URL: newURL, resolvingAgainstBaseURL: false) 116 | 117 | guard let items = components?.queryItems else { 118 | return 119 | } 120 | 121 | var infos = [String: AnyObject]() 122 | items.forEach { 123 | infos[$0.name] = $0.value 124 | } 125 | 126 | hideWebView(webView, tuples: (infos, nil, nil)) 127 | } 128 | 129 | func webView(webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) { 130 | 131 | guard let URL = webView.URL else { 132 | return 133 | } 134 | 135 | if let weiboServiceProvider = shareServiceProvider as? WeiboServiceProvier { 136 | if URL.absoluteString.lowercaseString.hasPrefix(weiboServiceProvider.redirectURL) { 137 | 138 | webView.stopLoading() 139 | 140 | guard let code = URL.queryInfo["code"] else { 141 | return 142 | } 143 | 144 | var accessTokenAPI = "https://api.weibo.com/oauth2/access_token?" 145 | accessTokenAPI += "client_id=" + weiboServiceProvider.appID 146 | accessTokenAPI += "&client_secret=" + weiboServiceProvider.appKey 147 | accessTokenAPI += "&grant_type=authorization_code&" 148 | accessTokenAPI += "redirect_uri=" + weiboServiceProvider.redirectURL 149 | accessTokenAPI += "&code=" + code 150 | 151 | 152 | Cupid.networkingProvier.request(accessTokenAPI, method: .POST, parameters: nil) { 153 | (dictionary, response, error) in dispatch_async(dispatch_get_main_queue()) { 154 | self.hideWebView(webView, tuples: (dictionary, response, error)) 155 | } 156 | } 157 | } 158 | } 159 | } 160 | 161 | func hideWebView(webView: WKWebView, tuples: (NSDictionary?, NSURLResponse?, NSError?)?) { 162 | 163 | activityIndicatorViewAction(webView, stop: true) 164 | webView.stopLoading() 165 | 166 | UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveEaseOut, animations: { 167 | webView.frame.origin.y = UIScreen.mainScreen().bounds.height 168 | 169 | }, completion: { 170 | _ in webView.removeFromSuperview() 171 | self.shareServiceProvider?.oauthCompletionHandler?(tuples?.0, tuples?.1, tuples?.2) 172 | }) 173 | } 174 | 175 | func activityIndicatorViewAction(webView: WKWebView, stop: Bool) { 176 | for subview in webView.scrollView.subviews { 177 | if let activityIndicatorView = subview as? UIActivityIndicatorView { 178 | guard stop else { 179 | activityIndicatorView.startAnimating() 180 | return 181 | } 182 | activityIndicatorView.stopAnimating() 183 | } 184 | } 185 | } 186 | } -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Network/URLHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLHandler.swift 3 | // China 4 | // 5 | // Created by Shannon Wu on 11/29/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct URLHandler { 12 | static func openURL(URLString URLString: String) -> Bool { 13 | guard let URL = NSURL(string: URLString) else { 14 | return false 15 | } 16 | 17 | return UIApplication.sharedApplication().openURL(URL) 18 | } 19 | 20 | static func canOpenURL(URL: NSURL?) -> Bool { 21 | guard let URL = URL else { 22 | return false 23 | } 24 | 25 | return UIApplication.sharedApplication().canOpenURL(URL) 26 | } 27 | } -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Network/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // China 4 | // 5 | // Created by Shannon Wu on 11/29/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSBundle { 12 | 13 | var displayName: String? { 14 | 15 | func getNameByInfo(info: [String:AnyObject]) -> String? { 16 | 17 | guard let displayName = info["CFBundleDisplayName"] as? String else { 18 | return info["CFBundleName"] as? String 19 | } 20 | 21 | return displayName 22 | } 23 | 24 | guard let info = localizedInfoDictionary ?? infoDictionary else { 25 | return nil 26 | } 27 | 28 | return getNameByInfo(info) 29 | } 30 | 31 | var bundleID: String? { 32 | return objectForInfoDictionaryKey("CFBundleIdentifier") as? String 33 | } 34 | } 35 | 36 | extension String { 37 | 38 | var base64EncodedString: String? { 39 | return dataUsingEncoding(NSUTF8StringEncoding)?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)) 40 | } 41 | 42 | var base64AndURLEncodedString: String? { 43 | return base64EncodedString?.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet()) 44 | } 45 | 46 | } 47 | 48 | extension NSURL { 49 | 50 | var queryInfo: [String:String] { 51 | 52 | var info = [String: String]() 53 | 54 | if let querys = query?.componentsSeparatedByString("&") { 55 | for query in querys { 56 | let keyValuePair = query.componentsSeparatedByString("=") 57 | if keyValuePair.count == 2 { 58 | let key = keyValuePair[0] 59 | let value = keyValuePair[1] 60 | 61 | info[key] = value 62 | } 63 | } 64 | } 65 | 66 | return info 67 | } 68 | } 69 | 70 | extension UIImage { 71 | var imageForShare: UIImage { 72 | get { 73 | let width: CGFloat = 90.0 74 | let height = size.height / size.width * 90.0 75 | let newSize = CGSize(width: width, height: height) 76 | UIGraphicsBeginImageContextWithOptions(newSize, false, 0) 77 | drawInRect(CGRect(origin: CGPointZero, size: newSize)) 78 | 79 | let scaledImage = UIGraphicsGetImageFromCurrentImageContext() 80 | UIGraphicsEndImageContext() 81 | 82 | return scaledImage 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Service Provider/Cupid+Protocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShareServiceProvider.swift 3 | // China 4 | // 5 | // Created by Shannon Wu on 11/29/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Callback after share, true if content is shared, false if content is not shared successfully 12 | public typealias ShareCompletionHandler = (succeed:Bool) -> Void 13 | /// Callback after network request finshed 14 | public typealias NetworkResponseHandler = (NSDictionary?, NSURLResponse?, NSError?) -> Void 15 | 16 | public protocol ShareServiceProvider: class { 17 | /// check if can OAuth right now 18 | static var canOAuth: Bool { get } 19 | /// Used to check if the content is sharable 20 | static func canShareContent(content: Content) -> Bool 21 | func canShareContent(content: Content) -> Bool 22 | /// Check if app is installed 23 | static var appInstalled: Bool { get } 24 | /// Used for OAuth callback 25 | var oauthCompletionHandler: NetworkResponseHandler? { get } 26 | /// Share content to service provider 27 | func shareContent(content: Content, completionHandler: ShareCompletionHandler?) throws 28 | /// OAuth 29 | func OAuth(completionHandler: NetworkResponseHandler) throws 30 | /// Callback after share or OAuth 31 | func handleOpenURL(URL: NSURL) -> Bool? 32 | } 33 | 34 | extension ShareServiceProvider { 35 | public static var appInstalled: Bool { 36 | return false 37 | } 38 | } 39 | 40 | /// Errors thrown in OAuth or share process 41 | public enum ShareError: ErrorType { 42 | /// Cannot share content 43 | case ContentCannotShare 44 | /// Cannot format the data successfully 45 | case FormattingError 46 | /// Service provider is not configured correctly. 47 | case InternalError 48 | /// Service destination is not configured. 49 | case DestinationNotPointed 50 | /// Host app is not installed, and cannot OAuth through web view 51 | case AppNotInstalled 52 | /// Service provider does not support this function 53 | case NotSupported 54 | } 55 | 56 | /// The netowrking methods needed for Cupid 57 | public enum CupidNetworkingMethod: String { 58 | case GET = "GET" 59 | case POST = "POST" 60 | } 61 | 62 | /// The networking provider 63 | public protocol CupidNetworkingProvider { 64 | /// Send network request with URL string 65 | func request(URLString: String, method: CupidNetworkingMethod, parameters: [String:AnyObject]?, completionHandler: NetworkResponseHandler) 66 | /// Send network request with URL 67 | func request(URL: NSURL, method: CupidNetworkingMethod, parameters: [String:AnyObject]?, completionHandler: NetworkResponseHandler) 68 | /// Used for uploading 69 | func upload(URL: NSURL, parameters: [String:AnyObject], completionHandler: NetworkResponseHandler) 70 | } -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Service Provider/PasteboardServiceProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasteboardServiceProvider.swift 3 | // Cupid Demo 4 | // 5 | // Created by Shannon Wu on 12/1/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | ///this service provider is used to copy content to the system paste board 12 | public class PasteboardServiceProvider: ShareServiceProvider { 13 | /// Always false 14 | public static var canOAuth: Bool { 15 | return false 16 | } 17 | 18 | public static var appInstalled: Bool { 19 | return true 20 | } 21 | 22 | /// Always nil 23 | public let oauthCompletionHandler: NetworkResponseHandler? = nil 24 | 25 | /// Init a new paste board service provider 26 | public init() {} 27 | 28 | /// Always true 29 | public static func canShareContent(content: Content) -> Bool { 30 | return true 31 | } 32 | 33 | /// Always true 34 | public func canShareContent(content: Content) -> Bool { 35 | return true 36 | } 37 | 38 | /// The pasteboard's string property is set to the content's title and the content's description 39 | /// If the media's type is URL, the pasteboard's URL property is set to the URL of the content's media 40 | /// If the media's type is Image, the pasteboard's image property is set to thhe image of the content's media 41 | /// If the media's type is Video, the pasteboard's URL property is set to the URL of the content's media 42 | /// If the media's type is Audio, the pasteboard's URLs property is set to the URLs of the content's media 43 | public func shareContent(content: Content, completionHandler: ShareCompletionHandler? = nil) throws { 44 | let pasterBoard = UIPasteboard.generalPasteboard() 45 | 46 | pasterBoard.string = "\(content.title ?? "") \(content.description ?? "")" 47 | 48 | if let media = content.media { 49 | switch media { 50 | case .URL(let URL): 51 | pasterBoard.URL = URL 52 | case .Image(let image): 53 | pasterBoard.image = image 54 | case .Video(let URL): 55 | pasterBoard.URL = URL 56 | case .Audio(audioURL: let audioURL, linkURL: let linkURL): 57 | if let linkURL = linkURL { 58 | pasterBoard.URLs = [audioURL, linkURL] 59 | } else { 60 | pasterBoard.URL = audioURL 61 | } 62 | } 63 | } 64 | 65 | completionHandler?(succeed: true) 66 | } 67 | 68 | /// Always throw ShareError.NotSupported 69 | public func OAuth(completionHandler: NetworkResponseHandler) throws { 70 | throw ShareError.NotSupported 71 | } 72 | 73 | /// Always returns nil 74 | public func handleOpenURL(URL: NSURL) -> Bool? { 75 | return nil 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Service Provider/PocketServiceProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PocketServiceProvider.swift 3 | // China 4 | // 5 | // Created by Shannon Wu on 11/30/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | ///This service provider is used to oauth and share content to Pocket, check [this website](https://getpocket.com/developer/docs/authentication) to learn more 12 | public class PocketServiceProvider: ShareServiceProvider { 13 | 14 | /// App id of the account 15 | public private(set) var appID: String 16 | /// Access token of the account 17 | public private(set) var accessToken: String? 18 | var requestToken: String? 19 | 20 | lazy var webviewProvider: SimpleWebView = { 21 | let webViewProvider = SimpleWebView() 22 | webViewProvider.shareServiceProvider = self 23 | return webViewProvider 24 | }() 25 | 26 | var shareCompletionHandler: ShareCompletionHandler? 27 | /// Hanldler after oauth 28 | public var oauthCompletionHandler: NetworkResponseHandler? 29 | 30 | /// Init a new service provider with the given information, if you want to share content, you must provide the access token. 31 | public init(appID: String, accessToken: String?) { 32 | self.appID = appID 33 | self.accessToken = accessToken 34 | } 35 | 36 | /// Always true 37 | public static var canOAuth: Bool { 38 | return true 39 | } 40 | 41 | /// The content's media type must be URL, or return false 42 | public static func canShareContent(content: Content) -> Bool { 43 | guard content.media != nil else { 44 | return false 45 | } 46 | 47 | switch content.media! { 48 | case .URL: 49 | return true 50 | 51 | default: 52 | return false 53 | } 54 | } 55 | 56 | /// The content's media type must be URL, or return false 57 | public func canShareContent(content: Content) -> Bool { 58 | return PocketServiceProvider.canShareContent(content) 59 | } 60 | 61 | public static var appInstalled: Bool { 62 | return URLHandler.canOpenURL(NSURL(string: "pocket-oauth-v1://")) 63 | } 64 | 65 | /// The content's title is used for the title of the article, and the content media's URL is set to the url of the article 66 | public func shareContent(content: Content, completionHandler: ShareCompletionHandler? = nil) throws { 67 | shareCompletionHandler = completionHandler 68 | 69 | guard PocketServiceProvider.canShareContent(content) else { 70 | throw ShareError.ContentCannotShare 71 | } 72 | 73 | guard let accessToken = accessToken else { 74 | throw ShareError.InternalError 75 | } 76 | 77 | guard let addAPI = NSURL(string: "https://getpocket.com/v3/add") else { 78 | throw ShareError.FormattingError 79 | } 80 | 81 | 82 | var parameters = ["consumer_key": appID, "access_token": accessToken] 83 | let URLString: String 84 | guard let media = content.media else { 85 | throw ShareError.ContentCannotShare 86 | } 87 | switch media { 88 | case .URL(let url): 89 | URLString = url.absoluteString 90 | default: 91 | throw ShareError.ContentCannotShare 92 | } 93 | 94 | parameters["url"] = URLString 95 | if let title = content.title { 96 | parameters["title"] = title 97 | } 98 | Cupid.networkingProvier.request(addAPI, method: .POST, parameters: parameters) { 99 | (dict, response, error) -> Void in 100 | if error != nil { 101 | self.shareCompletionHandler?(succeed: false) 102 | } 103 | else { 104 | self.shareCompletionHandler?(succeed: true) 105 | } 106 | } 107 | } 108 | 109 | /// OAuth to Pocket, the completion handler cantains the information from Pocket 110 | public func OAuth(completionHandler: NetworkResponseHandler) throws { 111 | oauthCompletionHandler = completionHandler 112 | 113 | guard let startIndex = appID.rangeOfString("-")?.startIndex else { 114 | throw ShareError.InternalError 115 | } 116 | 117 | let prefix = appID.substringToIndex(startIndex) 118 | guard let requestAPI = NSURL(string: "https://getpocket.com/v3/oauth/request") else { 119 | throw ShareError.FormattingError 120 | } 121 | let redirectURLString = "pocketapp\(prefix):authorizationFinished" 122 | 123 | let parameters = ["consumer_key": appID, "redirect_uri": redirectURLString] 124 | Cupid.networkingProvier.request(requestAPI, method: .POST, parameters: parameters) { 125 | (dictionary, response, error) -> Void in 126 | 127 | guard let requestToken = dictionary?["code"] as? String else { 128 | return 129 | } 130 | self.requestToken = requestToken 131 | 132 | if PocketServiceProvider.appInstalled { 133 | let requestTokenAPI = "pocket-oauth-v1:///authorize?request_token=\(requestToken)&redirect_uri=\(redirectURLString)" 134 | URLHandler.openURL(URLString: requestTokenAPI) 135 | return 136 | } else { 137 | let requestTokenAPI = "https://getpocket.com/auth/authorize?request_token=\(requestToken)&redirect_uri=\(redirectURLString)" 138 | dispatch_async(dispatch_get_main_queue()) { 139 | self.webviewProvider.addWebViewByURLString(requestTokenAPI, flagCode: requestToken) 140 | } 141 | } 142 | } 143 | 144 | } 145 | 146 | /// Handle URL callback for Pocket 147 | public func handleOpenURL(URL: NSURL) -> Bool? { 148 | if URL.scheme.hasPrefix("pocketapp") { 149 | guard let accessTokenAPI = NSURL(string: "https://getpocket.com/v3/oauth/authorize") else { 150 | return true 151 | } 152 | 153 | guard let requestToken = requestToken else { 154 | return true 155 | } 156 | 157 | let parameters = ["consumer_key": self.appID, "code": requestToken] 158 | Cupid.networkingProvier.request(accessTokenAPI, method: .POST, parameters: parameters) { 159 | (dictionary, response, error) -> Void in 160 | self.oauthCompletionHandler?(dictionary, response, error) 161 | } 162 | return true 163 | } 164 | 165 | return nil 166 | } 167 | } -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Service Provider/QQServiceProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QQServiceProvider.swift 3 | // China 4 | // 5 | // Created by Shannon Wu on 11/29/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | ///This service provider is used to oauth and share content to QQ, check [this website](http://open.qq.com) to learn more 12 | public class QQServiceProvider: ShareServiceProvider { 13 | /// Share destination 14 | public enum Destination: Int { 15 | // Share to QQ chat session 16 | case Friends = 0 17 | // Share to QZone 18 | case QZone = 1 19 | } 20 | 21 | public static var appInstalled: Bool { 22 | return URLHandler.canOpenURL(NSURL(string: "mqqapi://")) 23 | } 24 | 25 | /// Always true 26 | public static var canOAuth: Bool { 27 | return true 28 | } 29 | 30 | /// True if the app is installed 31 | public static func canShareContent(content: Content) -> Bool { 32 | if !QQServiceProvider.appInstalled { 33 | return false 34 | } 35 | 36 | return true 37 | } 38 | 39 | /// True if the app is installed 40 | public func canShareContent(content: Content) -> Bool { 41 | return QQServiceProvider.canShareContent(content) 42 | } 43 | 44 | lazy var webviewProvider: SimpleWebView = { 45 | let webViewProvider = SimpleWebView() 46 | webViewProvider.shareServiceProvider = self 47 | return webViewProvider 48 | }() 49 | 50 | /// App id 51 | public private(set) var appID: String 52 | /// Destination to share 53 | public private(set) var destination: Destination? 54 | /// Completion handler after share 55 | public private(set) var shareCompletionHandler: ShareCompletionHandler? 56 | /// Completion handler after OAuth 57 | public private(set) var oauthCompletionHandler: NetworkResponseHandler? 58 | 59 | /// Init a new service provider with the given information, if you want to share content, you must provide the share destination. 60 | public init(appID: String, destination: Destination? = nil) { 61 | self.appID = appID 62 | self.destination = destination 63 | } 64 | 65 | var callBackName: String { 66 | var hexString = String(format: "%02llx", (appID as NSString).longLongValue) 67 | while hexString.characters.count < 8 { 68 | hexString = "0" + hexString 69 | } 70 | 71 | return "QQ" + hexString 72 | } 73 | 74 | /// Share content to QQ, with a optional completion block 75 | /// The title of the content is used for the title of the shared message 76 | /// The description of the content is used for the brief intro of the shared message 77 | /// The thumbnail of the content is used for the thumbnail of the shared message 78 | /// The media is the payload of the shared message 79 | public func shareContent(content: Content, completionHandler: ShareCompletionHandler? = nil) throws { 80 | self.shareCompletionHandler = completionHandler 81 | 82 | guard QQServiceProvider.canShareContent(content) else { 83 | throw ShareError.ContentCannotShare 84 | } 85 | 86 | var qqSchemeURLString = "mqqapi://share/to_fri?" 87 | if let encodedAppDisplayName = NSBundle.mainBundle().displayName?.base64EncodedString { 88 | qqSchemeURLString += "thirdAppDisplayName=" + encodedAppDisplayName 89 | } 90 | else { 91 | throw ShareError.FormattingError 92 | } 93 | 94 | if let destination = destination { 95 | qqSchemeURLString += "&version=1&cflag=\(destination.rawValue)" 96 | } 97 | else { 98 | throw ShareError.DestinationNotPointed 99 | } 100 | qqSchemeURLString += "&callback_type=scheme&generalpastboard=1" 101 | qqSchemeURLString += "&callback_name=\(callBackName)" 102 | qqSchemeURLString += "&src_type=app&shareType=0&file_type=" 103 | 104 | if let media = content.media { 105 | 106 | func handleNewsWithURL(URL: NSURL, mediaType: String?) throws { 107 | if let thumbnail = content.thumbnail, thumbnailData = UIImageJPEGRepresentation(thumbnail, 1) { 108 | let dic = ["previewimagedata": thumbnailData] 109 | let data = NSKeyedArchiver.archivedDataWithRootObject(dic) 110 | UIPasteboard.generalPasteboard().setData(data, forPasteboardType: "com.tencent.mqq.api.apiLargeData") 111 | } 112 | 113 | qqSchemeURLString += mediaType ?? "news" 114 | guard let encodedURLString = URL.absoluteString.base64AndURLEncodedString else { 115 | throw ShareError.FormattingError 116 | } 117 | 118 | qqSchemeURLString += "&url=\(encodedURLString)" 119 | } 120 | 121 | switch media { 122 | case .URL(let URL): 123 | do { 124 | try handleNewsWithURL(URL, mediaType: "news") 125 | } 126 | catch let error { 127 | throw error 128 | } 129 | 130 | case .Image(let image): 131 | guard let imageData = UIImageJPEGRepresentation(image, 1) else { 132 | throw ShareError.FormattingError 133 | } 134 | var dic = ["file_data": imageData, ] 135 | if let thumbnail = content.thumbnail, thumbnailData = UIImageJPEGRepresentation(thumbnail, 1) { 136 | dic["previewimagedata"] = thumbnailData 137 | } 138 | let data = NSKeyedArchiver.archivedDataWithRootObject(dic) 139 | UIPasteboard.generalPasteboard().setData(data, forPasteboardType: "com.tencent.mqq.api.apiLargeData") 140 | qqSchemeURLString += "img" 141 | 142 | case .Audio(let audioURL, _ ): 143 | do { 144 | try handleNewsWithURL(audioURL, mediaType: "audio") 145 | } 146 | catch let error { 147 | throw error 148 | } 149 | 150 | case .Video(let URL): 151 | do { 152 | try handleNewsWithURL(URL, mediaType: nil) 153 | } 154 | catch let error { 155 | throw error 156 | } 157 | } 158 | 159 | if let encodedTitle = content.title?.base64AndURLEncodedString { 160 | qqSchemeURLString += "&title=\(encodedTitle)" 161 | } 162 | 163 | if let encodedDescription = content.description?.base64AndURLEncodedString { 164 | qqSchemeURLString += "&objectlocation=pasteboard&description=\(encodedDescription)" 165 | } 166 | 167 | } 168 | else { 169 | qqSchemeURLString += "text&file_data=" 170 | 171 | if let encodedDescription = content.description?.base64AndURLEncodedString { 172 | qqSchemeURLString += "\(encodedDescription)" 173 | } 174 | } 175 | 176 | if !URLHandler.openURL(URLString: qqSchemeURLString) { 177 | throw ShareError.FormattingError 178 | } 179 | } 180 | 181 | /// OAuth to QQ, the completion handler cantains the information from Pocket 182 | public func OAuth(completionHandler: NetworkResponseHandler) throws { 183 | oauthCompletionHandler = completionHandler 184 | 185 | let scope = "get_user_info" 186 | if QQServiceProvider.appInstalled { 187 | guard let appName = NSBundle.mainBundle().displayName else { 188 | throw ShareError.FormattingError 189 | } 190 | let dic = ["app_id": appID, "app_name": appName, "client_id": appID, "response_type": "token", "scope": scope, "sdkp": "i", "sdkv": "2.9", "status_machine": UIDevice.currentDevice().model, "status_os": UIDevice.currentDevice().systemVersion, "status_version": UIDevice.currentDevice().systemVersion] 191 | 192 | let data = NSKeyedArchiver.archivedDataWithRootObject(dic) 193 | UIPasteboard.generalPasteboard().setData(data, forPasteboardType: "com.tencent.tencent\(appID)") 194 | 195 | URLHandler.openURL(URLString: "mqqOpensdkSSoLogin://SSoLogin/tencent\(appID)/com.tencent.tencent\(appID)?generalpastboard=1") 196 | 197 | return 198 | } else { 199 | let accessTokenAPI = "http://xui.ptlogin2.qq.com/cgi-bin/xlogin?appid=716027609&pt_3rd_aid=209656&style=35&s_url=http%3A%2F%2Fconnect.qq.com&refer_cgi=m_authorize&client_id=\(appID)&redirect_uri=auth%3A%2F%2Fwww.qq.com&response_type=token&scope=\(scope)" 200 | 201 | webviewProvider.addWebViewByURLString(accessTokenAPI) 202 | } 203 | } 204 | 205 | /// Handle URL callback for QQ 206 | public func handleOpenURL(URL: NSURL) -> Bool? { 207 | // QQ Share 208 | if URL.scheme.hasPrefix("QQ") { 209 | guard let error = URL.queryInfo["error"] else { 210 | return false 211 | } 212 | let succeed = (error == "0") 213 | 214 | shareCompletionHandler?(succeed: succeed) 215 | return succeed 216 | } 217 | 218 | if URL.scheme.hasPrefix("tencent") { 219 | 220 | var userInfoDictionary: NSDictionary? 221 | var error: NSError? 222 | 223 | defer { 224 | oauthCompletionHandler?(userInfoDictionary, nil, error) 225 | } 226 | 227 | guard let data = UIPasteboard.generalPasteboard().dataForPasteboardType("com.tencent.tencent\(appID)"), let dic = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? NSDictionary else { 228 | error = NSError(domain: "OAuth Error", code: -1, userInfo: nil) 229 | return false 230 | } 231 | 232 | guard let result = dic["ret"]?.integerValue where result == 0 else { 233 | if let errorDomatin = dic["user_cancelled"] as? String where errorDomatin == "YES" { 234 | error = NSError(domain: "User Cancelled", code: -2, userInfo: nil) 235 | } 236 | else { 237 | error = NSError(domain: "OAuth Error", code: -1, userInfo: nil) 238 | } 239 | return false 240 | } 241 | 242 | userInfoDictionary = dic 243 | 244 | return true 245 | } 246 | 247 | // Other 248 | return nil 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Service Provider/WeChatServiceProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeChatServiceProvider.swift 3 | // China 4 | // 5 | // Created by Shannon Wu on 11/29/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | ///This service provider is used to oauth and share content to WeChat, check [this website](https://open.weixin.qq.com) to learn more 12 | public class WeChatServiceProvier: ShareServiceProvider { 13 | /// Share destination 14 | public enum Destination: Int { 15 | /// Share to chat session 16 | case Session = 0 17 | /// Share to moments 18 | case Timeline = 1 19 | } 20 | 21 | public static var appInstalled: Bool { 22 | return URLHandler.canOpenURL(NSURL(string: "weixin://")) 23 | } 24 | 25 | /// True if app is installed 26 | public static var canOAuth: Bool { 27 | return appInstalled 28 | } 29 | 30 | 31 | /// App id 32 | public private(set) var appID: String 33 | /// App key 34 | public private(set) var appKey: String? 35 | /// Destination to share 36 | public private(set) var destination: Destination? 37 | /// Completion handler after share 38 | public private(set) var shareCompletionHandler: ShareCompletionHandler? 39 | /// Completion handler after OAuth 40 | public private(set) var oauthCompletionHandler: NetworkResponseHandler? 41 | 42 | /// Init a new service provider with the given information, if you want to share content, you must provide the share destination. 43 | public init(appID: String, appKey: String?, destination: Destination? = nil) { 44 | self.appID = appID 45 | self.appKey = appKey 46 | self.destination = destination 47 | } 48 | 49 | /// True if app is installed 50 | public static func canShareContent(content: Content) -> Bool { 51 | return appInstalled 52 | } 53 | 54 | /// True if app is installed 55 | public func canShareContent(content: Content) -> Bool { 56 | return WeChatServiceProvier.canShareContent(content) 57 | } 58 | 59 | /// Share content to WeChat, with a optional completion block 60 | /// The title of the content is used for the title of the shared message 61 | /// The description of the content is used for the brief intro of the shared message 62 | /// The thumbnail of the content is used for the thumbnail of the shared message 63 | /// The media is the payload of the shared message 64 | public func shareContent(content: Content, completionHandler: ShareCompletionHandler? = nil) throws { 65 | self.shareCompletionHandler = completionHandler 66 | 67 | guard WeChatServiceProvier.canShareContent(content) else { 68 | throw ShareError.ContentCannotShare 69 | } 70 | 71 | var weChatMessageInfo: [String:AnyObject] 72 | if let destination = destination { 73 | weChatMessageInfo = ["result": "1", "returnFromApp": "0", "scene": destination.rawValue, "sdkver": "1.5", "command": "1010", ] 74 | } 75 | else { 76 | throw ShareError.DestinationNotPointed 77 | } 78 | 79 | if let title = content.title { 80 | weChatMessageInfo["title"] = title 81 | } 82 | 83 | if let description = content.description { 84 | weChatMessageInfo["description"] = description 85 | } 86 | 87 | if let thumbnailImage = content.thumbnail, 88 | let thumbnailData = UIImageJPEGRepresentation(thumbnailImage, 0.5) { 89 | weChatMessageInfo["thumbData"] = thumbnailData 90 | } 91 | 92 | if let media = content.media { 93 | switch media { 94 | case .URL(let URL): 95 | weChatMessageInfo["objectType"] = "5" 96 | weChatMessageInfo["mediaUrl"] = URL.absoluteString 97 | 98 | case .Image(let image): 99 | weChatMessageInfo["objectType"] = "2" 100 | 101 | if let fileImageData = UIImageJPEGRepresentation(image, 1) { 102 | weChatMessageInfo["fileData"] = fileImageData 103 | } 104 | 105 | case .Audio(let audioURL, let linkURL): 106 | weChatMessageInfo["objectType"] = "3" 107 | 108 | if let linkURL = linkURL { 109 | weChatMessageInfo["mediaUrl"] = linkURL.absoluteString 110 | } 111 | 112 | weChatMessageInfo["mediaDataUrl"] = audioURL.absoluteString 113 | 114 | case .Video(let URL): 115 | weChatMessageInfo["objectType"] = "4" 116 | weChatMessageInfo["mediaUrl"] = URL.absoluteString 117 | } 118 | 119 | } 120 | else { 121 | weChatMessageInfo["command"] = "1020" 122 | } 123 | 124 | let weChatMessage = [appID: weChatMessageInfo] 125 | 126 | guard let data = try? NSPropertyListSerialization.dataWithPropertyList(weChatMessage, format: .BinaryFormat_v1_0, options: 0) else { 127 | throw ShareError.FormattingError 128 | } 129 | 130 | UIPasteboard.generalPasteboard().setData(data, forPasteboardType: "content") 131 | 132 | let weChatSchemeURLString = "weixin://app/\(appID)/sendreq/?" 133 | 134 | if !URLHandler.openURL(URLString: weChatSchemeURLString) { 135 | throw ShareError.FormattingError 136 | } 137 | } 138 | 139 | /// OAuth to Wechat, the completion handler cantains the information from Wechat 140 | public func OAuth(completionHandler: NetworkResponseHandler) throws { 141 | oauthCompletionHandler = completionHandler 142 | 143 | guard WeChatServiceProvier.appInstalled else { 144 | throw ShareError.AppNotInstalled 145 | } 146 | 147 | let scope = "snsapi_userinfo" 148 | URLHandler.openURL(URLString: "weixin://app/\(appID)/auth/?scope=\(scope)&state=Weixinauth") 149 | } 150 | 151 | func fetchWeChatOAuthInfoByCode(code code: String, completionHandler: NetworkResponseHandler) { 152 | guard let key = appKey else { 153 | completionHandler(["code": code], nil, nil) 154 | return 155 | } 156 | 157 | var accessTokenAPI = "https://api.weixin.qq.com/sns/oauth2/access_token?" 158 | accessTokenAPI += "appid=" + appID 159 | accessTokenAPI += "&secret=" + key 160 | accessTokenAPI += "&code=" + code + "&grant_type=authorization_code" 161 | 162 | Cupid.networkingProvier.request(accessTokenAPI, method: CupidNetworkingMethod.GET, parameters: nil) { 163 | (OAuthJSON, response, error) -> Void in 164 | completionHandler(OAuthJSON, response, error) 165 | } 166 | } 167 | 168 | /// Handle URL callback for Wechat 169 | public func handleOpenURL(URL: NSURL) -> Bool? { 170 | if URL.scheme.hasPrefix("wx") { 171 | // WeChat OAuth 172 | if URL.absoluteString.containsString("&state=Weixinauth") { 173 | let components = NSURLComponents(URL: URL, resolvingAgainstBaseURL: false) 174 | 175 | guard let items = components?.queryItems else { 176 | return false 177 | } 178 | 179 | var infos = [String: AnyObject]() 180 | items.forEach { 181 | infos[$0.name] = $0.value 182 | } 183 | 184 | guard let code = infos["code"] as? String else { 185 | return false 186 | } 187 | 188 | // Login Succcess 189 | fetchWeChatOAuthInfoByCode(code: code) { 190 | (info, response, error) -> Void in 191 | self.oauthCompletionHandler?(info, response, error) 192 | } 193 | return true 194 | } 195 | 196 | // WeChat Share 197 | guard let data = UIPasteboard.generalPasteboard().dataForPasteboardType("content") else { 198 | return false 199 | } 200 | 201 | if let dic = try? NSPropertyListSerialization.propertyListWithData(data, options: .Immutable, format: nil) { 202 | if let dic = dic[appID] as? NSDictionary, 203 | result = dic["result"]?.integerValue { 204 | let succeed = (result == 0) 205 | shareCompletionHandler?(succeed: succeed) 206 | return succeed 207 | } 208 | } 209 | } 210 | 211 | // Other 212 | return nil 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /CupidDemo/Cupid/Cupid/Service Provider/WeiboServiceProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeiboServiceProvider.swift 3 | // China 4 | // 5 | // Created by Shannon Wu on 11/29/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | ///This service provider is used to oauth and share content to Weibo, check [this website](http://open.weibo.com) to learn more 12 | public class WeiboServiceProvier: ShareServiceProvider { 13 | public static var appInstalled: Bool { 14 | return URLHandler.canOpenURL(NSURL(string: "weibosdk://request")) 15 | } 16 | 17 | /// Always true 18 | public static var canOAuth: Bool { 19 | return true 20 | } 21 | 22 | lazy var webviewProvider: SimpleWebView = { 23 | let webViewProvider = SimpleWebView() 24 | webViewProvider.shareServiceProvider = self 25 | return webViewProvider 26 | }() 27 | 28 | /// App id 29 | public private(set) var appID: String 30 | /// App key 31 | public private(set) var appKey: String 32 | /// Access token 33 | public private(set) var accessToken: String? 34 | /// Redirect URL 35 | public private(set) var redirectURL: String 36 | /// Completion handler after share 37 | public private(set) var shareCompletionHandler: ShareCompletionHandler? 38 | /// Completion handler after OAuth 39 | public private(set) var oauthCompletionHandler: NetworkResponseHandler? 40 | 41 | /// Init a new service provider with the given information, if you want to share content, you must provide the access token. 42 | public init(appID: String, appKey: String, redirectURL: String, accessToken: String? = nil) { 43 | self.appID = appID 44 | self.appKey = appKey 45 | self.redirectURL = redirectURL 46 | self.accessToken = accessToken 47 | } 48 | 49 | /// False if both the content's description and media payload are nil, this will open Weibo but return immediately 50 | public static func canShareContent(content: Content) -> Bool { 51 | if (content.description == nil && content.media == nil) { 52 | return false 53 | } 54 | 55 | if let media = content.media { 56 | switch media { 57 | case .Audio, 58 | .Video: 59 | return false 60 | default: 61 | return true 62 | } 63 | } 64 | 65 | return true 66 | } 67 | 68 | /// False if both the content's description and media payload are nil, this will open Weibo but return immediately 69 | public func canShareContent(content: Content) -> Bool { 70 | return WeiboServiceProvier.canShareContent(content) 71 | } 72 | 73 | /// Share content to WeChat, with a optional completion block 74 | /// The title of the content is used for the title of the shared message 75 | /// The description of the content is used for the placehodler text of the shared message 76 | /// The thumbnail of the content is used for the thumbnail of the shared message 77 | /// The media is the payload of the shared message, audio and video is not supported right now 78 | public func shareContent(content: Content, completionHandler: ShareCompletionHandler? = nil) throws { 79 | self.shareCompletionHandler = completionHandler 80 | 81 | guard WeiboServiceProvier.canShareContent(content) else { 82 | throw ShareError.ContentCannotShare 83 | } 84 | 85 | guard !URLHandler.canOpenURL(NSURL(string: "weibosdk://request")) else { 86 | var messageInfo: [String:AnyObject] = ["__class": "WBMessageObject"] 87 | 88 | if let title = content.title { 89 | messageInfo["text"] = title 90 | } 91 | 92 | if let media = content.media { 93 | switch media { 94 | case .URL(let URL): 95 | var mediaObject: [String:AnyObject] = ["__class": "WBWebpageObject", "objectID": "identifier1"] 96 | 97 | if let desc = content.description { 98 | mediaObject["title"] = desc 99 | } 100 | 101 | if let thumbnailImage = content.thumbnail, let thumbnailData = UIImageJPEGRepresentation(thumbnailImage, 0.7) { 102 | mediaObject["thumbnailData"] = thumbnailData 103 | } 104 | 105 | mediaObject["webpageUrl"] = URL.absoluteString 106 | 107 | messageInfo["mediaObject"] = mediaObject 108 | 109 | case .Image(let image): 110 | if let imageData = UIImageJPEGRepresentation(image, 1.0) { 111 | messageInfo["imageObject"] = ["imageData": imageData] 112 | } 113 | 114 | case .Audio: 115 | throw ShareError.ContentCannotShare 116 | 117 | case .Video: 118 | throw ShareError.ContentCannotShare 119 | } 120 | } 121 | 122 | let uuIDString = CFUUIDCreateString(nil, CFUUIDCreate(nil)) 123 | let dict = ["__class": "WBSendMessageToWeiboRequest", "message": messageInfo, "requestID": uuIDString] 124 | 125 | let messageData: [AnyObject] = [["transferObject": NSKeyedArchiver.archivedDataWithRootObject(dict)], ["app": NSKeyedArchiver.archivedDataWithRootObject(["appKey": appID, "bundleID": NSBundle.mainBundle().bundleID ?? ""])]] 126 | 127 | UIPasteboard.generalPasteboard().items = messageData 128 | 129 | if !URLHandler.openURL(URLString: "weibosdk://request?id=\(uuIDString)&sdkversion=003013000") { 130 | throw ShareError.FormattingError 131 | } 132 | 133 | return 134 | } 135 | 136 | // Web Share 137 | 138 | var parameters = [String: AnyObject]() 139 | 140 | guard let accessToken = accessToken else { 141 | throw ShareError.InternalError 142 | } 143 | 144 | parameters["access_token"] = accessToken 145 | 146 | var statusText = "" 147 | 148 | if let title = content.title { 149 | statusText += title 150 | } 151 | 152 | if let description = content.description { 153 | statusText += description 154 | } 155 | 156 | var mediaType = Content.Media.URL(NSURL()) 157 | 158 | if let media = content.media { 159 | 160 | switch media { 161 | 162 | case .URL(let URL): 163 | 164 | statusText += URL.absoluteString 165 | 166 | mediaType = Content.Media.URL(URL) 167 | 168 | case .Image(let image): 169 | 170 | guard let imageData = UIImageJPEGRepresentation(image, 0.7) else { 171 | ShareError.FormattingError 172 | return 173 | } 174 | 175 | parameters["pic"] = imageData 176 | mediaType = Content.Media.Image(image) 177 | 178 | case .Audio: 179 | ShareError.ContentCannotShare 180 | 181 | case .Video: 182 | ShareError.ContentCannotShare 183 | } 184 | } 185 | 186 | parameters["status"] = statusText 187 | 188 | switch mediaType { 189 | 190 | case .URL(_ ): 191 | let URLString = "https://api.weibo.com/2/statuses/update.json" 192 | Cupid.networkingProvier.request(URLString, method: .POST, parameters: parameters) { 193 | (responseData, HTTPResponse, error) -> Void in if let JSON = responseData, let _ = JSON["idstr"] as? String { 194 | completionHandler?(succeed: true) 195 | } 196 | else { 197 | completionHandler?(succeed: false) 198 | } 199 | } 200 | 201 | case .Image(_ ): 202 | let URLString = "https://upload.api.weibo.com/2/statuses/upload.json" 203 | guard let URL = NSURL(string: URLString) else { 204 | ShareError.FormattingError 205 | return 206 | } 207 | 208 | Cupid.networkingProvier.upload(URL, parameters: parameters) { 209 | (responseData, HTTPResponse, error) -> Void in if let JSON = responseData, let _ = JSON["idstr"] as? String { 210 | completionHandler?(succeed: true) 211 | } 212 | else { 213 | completionHandler?(succeed: false) 214 | } 215 | } 216 | 217 | case .Audio: 218 | ShareError.ContentCannotShare 219 | 220 | case .Video: 221 | ShareError.ContentCannotShare 222 | } 223 | } 224 | 225 | /// OAuth to Wechat, the completion handler cantains the information from Weibo 226 | public func OAuth(completionHandler: NetworkResponseHandler) throws { 227 | self.oauthCompletionHandler = completionHandler 228 | 229 | let scope = "all" 230 | 231 | guard !WeiboServiceProvier.appInstalled else { 232 | let uuIDString = CFUUIDCreateString(nil, CFUUIDCreate(nil)) 233 | let authData = [["transferObject": NSKeyedArchiver.archivedDataWithRootObject(["__class": "WBAuthorizeRequest", "redirectURI": redirectURL, "requestID": uuIDString, "scope": scope])], ["userInfo": NSKeyedArchiver.archivedDataWithRootObject(["mykey": "as you like", "SSO_From": "SendMessageToWeiboViewController"])], ["app": NSKeyedArchiver.archivedDataWithRootObject(["appKey": appID, "bundleID": NSBundle.mainBundle().bundleID ?? "", "name": NSBundle.mainBundle().displayName ?? ""])]] 234 | 235 | UIPasteboard.generalPasteboard().items = authData 236 | URLHandler.openURL(URLString: "weibosdk://request?id=\(uuIDString)&sdkversion=003013000") 237 | return 238 | } 239 | 240 | let accessTokenAPI = "https://open.weibo.cn/oauth2/authorize?client_id=\(appID)&response_type=code&redirect_uri=\(redirectURL)&scope=\(scope)" 241 | webviewProvider.addWebViewByURLString(accessTokenAPI) 242 | } 243 | 244 | /// Handle URL callback for Weibo 245 | public func handleOpenURL(URL: NSURL) -> Bool? { 246 | if URL.scheme.hasPrefix("wb") { 247 | guard let items = UIPasteboard.generalPasteboard().items as? [[String:AnyObject]] else { 248 | return false 249 | } 250 | 251 | var results = [String: AnyObject]() 252 | 253 | for item in items { 254 | for (key, value) in item { 255 | if let valueData = value as? NSData where key == "transferObject" { 256 | results[key] = NSKeyedUnarchiver.unarchiveObjectWithData(valueData) 257 | } 258 | } 259 | } 260 | 261 | guard let responseData = results["transferObject"] as? [String:AnyObject], let type = responseData["__class"] as? String else { 262 | return false 263 | } 264 | 265 | guard let statusCode = responseData["statusCode"] as? Int else { 266 | return false 267 | } 268 | 269 | switch type { 270 | 271 | case "WBAuthorizeResponse": 272 | var userInfoDictionary: NSDictionary? 273 | var error: NSError? 274 | 275 | defer { 276 | oauthCompletionHandler?(responseData, nil, error) 277 | } 278 | 279 | userInfoDictionary = responseData 280 | 281 | if statusCode != 0 { 282 | error = NSError(domain: "OAuth Error", code: -1, userInfo: nil) 283 | return false 284 | } 285 | return true 286 | 287 | case "WBSendMessageToWeiboResponse": 288 | let succeed = (statusCode == 0) 289 | shareCompletionHandler?(succeed: succeed) 290 | 291 | return succeed 292 | default: 293 | return false 294 | } 295 | 296 | } 297 | 298 | // Other 299 | return nil 300 | } 301 | 302 | } -------------------------------------------------------------------------------- /CupidDemo/Cupid/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo.xcodeproj/xcshareddata/xcschemes/Cupid.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CupidDemo 4 | // 5 | // Created by Shannon Wu on 12/2/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | 18 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject:AnyObject]?) -> Bool { 19 | return true 20 | } 21 | 22 | func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool { 23 | // Check if it is callback for ShareManager, if it is return the flag. 24 | if let shareFlag = ShareManager.handleOpenURL(url) { 25 | return shareFlag 26 | } 27 | 28 | return false 29 | } 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "Untitled@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "Untitled@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "40x40", 17 | "idiom" : "iphone", 18 | "filename" : "Untitled@2x-1.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "Untitled@3x-1.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "60x60", 29 | "idiom" : "iphone", 30 | "filename" : "Untitled@2x-2.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Untitled@3x-3.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "29x29", 41 | "idiom" : "ipad", 42 | "filename" : "Untitled-1.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "29x29", 47 | "idiom" : "ipad", 48 | "filename" : "Untitled@2x-4.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "40x40", 53 | "idiom" : "ipad", 54 | "filename" : "Untitled-2.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "40x40", 59 | "idiom" : "ipad", 60 | "filename" : "Untitled@2x-5.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "76x76", 65 | "idiom" : "ipad", 66 | "filename" : "Untitled.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "76x76", 71 | "idiom" : "ipad", 72 | "filename" : "Untitled@2x-7.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "60x60", 77 | "idiom" : "car", 78 | "filename" : "Untitled@2x-6.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "60x60", 83 | "idiom" : "car", 84 | "filename" : "Untitled@3x-2.png", 85 | "scale" : "3x" 86 | }, 87 | { 88 | "size" : "24x24", 89 | "idiom" : "watch", 90 | "scale" : "2x", 91 | "role" : "notificationCenter", 92 | "subtype" : "38mm" 93 | }, 94 | { 95 | "size" : "27.5x27.5", 96 | "idiom" : "watch", 97 | "scale" : "2x", 98 | "role" : "notificationCenter", 99 | "subtype" : "42mm" 100 | }, 101 | { 102 | "size" : "29x29", 103 | "idiom" : "watch", 104 | "filename" : "Untitled@2x-3.png", 105 | "role" : "companionSettings", 106 | "scale" : "2x" 107 | }, 108 | { 109 | "size" : "29x29", 110 | "idiom" : "watch", 111 | "role" : "companionSettings", 112 | "scale" : "3x" 113 | }, 114 | { 115 | "size" : "40x40", 116 | "idiom" : "watch", 117 | "scale" : "2x", 118 | "role" : "appLauncher", 119 | "subtype" : "38mm" 120 | }, 121 | { 122 | "size" : "86x86", 123 | "idiom" : "watch", 124 | "scale" : "2x", 125 | "role" : "quickLook", 126 | "subtype" : "38mm" 127 | }, 128 | { 129 | "size" : "98x98", 130 | "idiom" : "watch", 131 | "scale" : "2x", 132 | "role" : "quickLook", 133 | "subtype" : "42mm" 134 | } 135 | ], 136 | "info" : { 137 | "version" : 1, 138 | "author" : "xcode" 139 | } 140 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled-1.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled-2.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-1.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-2.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-3.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-4.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-5.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-6.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x-7.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@2x.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@3x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@3x-1.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@3x-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@3x-2.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@3x-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@3x-3.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/AppIcon.appiconset/Untitled@3x.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/Cupid.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Cupid.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/Cupid.imageset/Cupid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/Cupid.imageset/Cupid.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_alipay.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "sns_share_alipay@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "sns_share_alipay@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_alipay.imageset/sns_share_alipay@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/sns_share_alipay.imageset/sns_share_alipay@2x.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_alipay.imageset/sns_share_alipay@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/sns_share_alipay.imageset/sns_share_alipay@3x.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_copy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "news-icon-share-link@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "news-icon-share-link@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_copy.imageset/news-icon-share-link@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/sns_share_copy.imageset/news-icon-share-link@2x.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_copy.imageset/news-icon-share-link@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/sns_share_copy.imageset/news-icon-share-link@3x.png -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_email.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "mail@1x-58x58.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_email.imageset/mail@1x-58x58.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/sns_share_email.imageset/mail@1x-58x58.pdf -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_friends.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "moment@1x-58x58.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_friends.imageset/moment@1x-58x58.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/sns_share_friends.imageset/moment@1x-58x58.pdf -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_message.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "message@1x-58x58.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_message.imageset/message@1x-58x58.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/sns_share_message.imageset/message@1x-58x58.pdf -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_qq.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "QQ@1x-58x58.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_qq.imageset/QQ@1x-58x58.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/sns_share_qq.imageset/QQ@1x-58x58.pdf -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_weibo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "weibo@1x-58x58.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_weibo.imageset/weibo@1x-58x58.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/sns_share_weibo.imageset/weibo@1x-58x58.pdf -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_weixin.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "wechat@1x-58x58.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Assets.xcassets/sns_share_weixin.imageset/wechat@1x-58x58.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Assets.xcassets/sns_share_weixin.imageset/wechat@1x-58x58.pdf -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 52 | 64 | 76 | 88 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Alipay-Bridging-Header.h 3 | // China 4 | // 5 | // Created by Shannon Wu on 12/1/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | #ifndef Alipay_Bridging_Header_h 10 | #define Alipay_Bridging_Header_h 11 | 12 | #import "APOpenAPI.h" 13 | #import "APOpenAPIObject.h" 14 | 15 | #endif /* Alipay_Bridging_Header_h */ 16 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Cupid 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleURLTypes 24 | 25 | 26 | CFBundleTypeRole 27 | Editor 28 | CFBundleURLName 29 | WeChat 30 | CFBundleURLSchemes 31 | 32 | wx4868b35061f87885 33 | 34 | 35 | 36 | CFBundleTypeRole 37 | Editor 38 | CFBundleURLName 39 | QQ 40 | CFBundleURLSchemes 41 | 42 | tencent1104999026 43 | 44 | 45 | 46 | CFBundleTypeRole 47 | Editor 48 | CFBundleURLName 49 | Weibo 50 | CFBundleURLSchemes 51 | 52 | wb4005151997 53 | 54 | 55 | 56 | CFBundleTypeRole 57 | Editor 58 | CFBundleURLName 59 | alipayShare 60 | CFBundleURLSchemes 61 | 62 | apxxxxxxxxxxxxxxxx 63 | 64 | 65 | 66 | CFBundleVersion 67 | 1 68 | LSApplicationQueriesSchemes 69 | 70 | alipayshare 71 | alipay 72 | weixin 73 | mqqapi 74 | weibosdk 75 | 76 | LSRequiresIPhoneOS 77 | 78 | NSAppTransportSecurity 79 | 80 | NSExceptionDomains 81 | 82 | api.weibo.com 83 | 84 | NSIncludesSubdomains 85 | 86 | NSThirdPartyExceptionMinimumTLSVersion 87 | TLSv1.0 88 | NSThirdPartyExceptionRequiresForwardSecrecy 89 | 90 | 91 | open.weibo.cn 92 | 93 | NSIncludesSubdomains 94 | 95 | NSThirdPartyExceptionMinimumTLSVersion 96 | TLSv1.0 97 | NSThirdPartyExceptionRequiresForwardSecrecy 98 | 99 | 100 | qq.com 101 | 102 | NSExceptionAllowsInsecureHTTPLoads 103 | 104 | NSIncludesSubdomains 105 | 106 | NSThirdPartyExceptionMinimumTLSVersion 107 | TLSv1.0 108 | NSThirdPartyExceptionRequiresForwardSecrecy 109 | 110 | 111 | sina.com.cn 112 | 113 | NSIncludesSubdomains 114 | 115 | NSThirdPartyExceptionMinimumTLSVersion 116 | TLSv1.0 117 | NSThirdPartyExceptionRequiresForwardSecrecy 118 | 119 | 120 | 121 | 122 | UILaunchStoryboardName 123 | LaunchScreen 124 | UIMainStoryboardFile 125 | Main 126 | UIRequiredDeviceCapabilities 127 | 128 | armv7 129 | 130 | UIRequiresFullScreen 131 | 132 | UISupportedInterfaceOrientations 133 | 134 | UIInterfaceOrientationPortrait 135 | UIInterfaceOrientationLandscapeLeft 136 | UIInterfaceOrientationLandscapeRight 137 | 138 | UISupportedInterfaceOrientations~ipad 139 | 140 | UIInterfaceOrientationPortrait 141 | UIInterfaceOrientationPortraitUpsideDown 142 | UIInterfaceOrientationLandscapeLeft 143 | UIInterfaceOrientationLandscapeRight 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainViewController.swift 3 | // Cupid Demo 4 | // 5 | // Created by Shannon Wu on 12/1/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MainViewController: UIViewController { 12 | // Types 13 | struct Media { 14 | static let URL = ShareContent.Media.URL(NSURL(string: "http://www.36kr.com")!) 15 | static let image = ShareContent.Media.Image(UIImage(named: "Cupid")!) 16 | static let audio = ShareContent.Media.Audio(audioURL: NSURL(string: "http://y.qq.com/#type=song&mid=001iZnof2dRaPm")!, linkURL: nil) 17 | static let video = ShareContent.Media.Video(NSURL(string: "http://v.youku.com/v_show/id_XOTU2MzA0NzY4.html")!) 18 | } 19 | 20 | // Properties 21 | var content = ShareContent() 22 | let destinationMapper = [1: ShareManager.Destination.Alipay, 23 | 2: ShareManager.Destination.Pasteboard, 24 | 3: ShareManager.Destination.Weibo, 25 | 4: ShareManager.Destination.WechatSession, 26 | 5: ShareManager.Destination.WechatTimeline, 27 | 6: ShareManager.Destination.QQ] 28 | let OAuthMapper = [3: ShareManager.OAuthType.Weibo, 29 | 4: ShareManager.OAuthType.Wechat, 30 | 6:ShareManager.OAuthType.QQ] 31 | 32 | @IBOutlet weak var shareOrOAuthSegmentControl: UISegmentedControl! 33 | @IBOutlet weak var mediaTypeSegmentControl: UISegmentedControl! 34 | 35 | @IBAction func chooseMedia(sender: UISegmentedControl) { 36 | switch sender.selectedSegmentIndex { 37 | case 0: 38 | content.media = Media.URL 39 | case 1: 40 | content.media = Media.image 41 | case 2: 42 | content.media = Media.audio 43 | case 3: 44 | content.media = Media.video 45 | default: () 46 | } 47 | updateShareButtonState() 48 | } 49 | 50 | @IBAction func toggleShareOAuth(sender: UISegmentedControl) { 51 | if sender.selectedSegmentIndex == 1 { 52 | mediaTypeSegmentControl.hidden = true 53 | } else { 54 | mediaTypeSegmentControl.hidden = false 55 | } 56 | updateShareButtonState() 57 | } 58 | 59 | 60 | 61 | @IBAction func share(sender: UIButton) { 62 | switch shareOrOAuthSegmentControl.selectedSegmentIndex { 63 | case 0: 64 | if let destination = destinationMapper[sender.tag] { 65 | ShareManager.shareContent(content, to: destination, 66 | succeed: { 67 | NotificationManager.showSuccessMessage("Share Succeed") 68 | }, 69 | failed: { 70 | NotificationManager.showErrorMessage("Share Failed") 71 | }) 72 | } 73 | case 1: 74 | if let OAuthType = OAuthMapper[sender.tag] { 75 | ShareManager.OAuth(OAuthType, 76 | succeeded: { (response) -> Void in 77 | NotificationManager.showSuccessMessage("OAuth succeed: \(response)") 78 | }, 79 | failed: { (errorMessage) -> Void in 80 | NotificationManager.showErrorMessage("OAuth failed: \(errorMessage)") 81 | }) 82 | } 83 | default: () 84 | } 85 | } 86 | 87 | override func viewDidLoad() { 88 | super.viewDidLoad() 89 | 90 | content.title = "36Kr" 91 | content.description = "The platform for entrepreneur" 92 | content.thumbnail = UIImage(named: "Cupid") 93 | content.media = Media.URL 94 | 95 | updateShareButtonState() 96 | } 97 | 98 | 99 | // Share 100 | private func updateShareButtonState() { 101 | guard view != nil else { return } 102 | for i in 1...6 { 103 | if let button = view.viewWithTag(i) as? UIButton { 104 | switch shareOrOAuthSegmentControl.selectedSegmentIndex { 105 | // Share 106 | case 0: 107 | if let destination = destinationMapper[i] { 108 | button.enabled = destination.serviceProviderType.canShareContent(content) 109 | } else { 110 | button.enabled = false 111 | } 112 | 113 | // OAuth 114 | case 1: 115 | if let OAuthType = OAuthMapper[i] { 116 | button.enabled = OAuthType.serviceProviderType.canOAuth 117 | } else { 118 | button.enabled = false 119 | } 120 | 121 | default: () 122 | } 123 | } 124 | } 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Notification Manager/NotificationManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationManager.swift 3 | // Cupid Demo 4 | // 5 | // Created by Shannon Wu on 12/1/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import StatusBarNotificationCenter 11 | 12 | struct NotificationManager { 13 | static func showSuccessMessage(message: String, duration: NSTimeInterval = 1.0) { 14 | var labelConfiguration = NotificationLabelConfiguration() 15 | labelConfiguration.backgroundColor = UIColor.blueColor() 16 | labelConfiguration.scrollSpeed = 160.0 17 | 18 | var notificationCenterConfiguration = NotificationCenterConfiguration(baseWindow: UIApplication.sharedApplication().keyWindow!) 19 | notificationCenterConfiguration.level = UIWindowLevelStatusBar 20 | 21 | StatusBarNotificationCenter.showStatusBarNotificationWithMessage(message, forDuration: duration, withNotificationCenterConfiguration: notificationCenterConfiguration, andNotificationLabelConfiguration: labelConfiguration) 22 | } 23 | 24 | static func showErrorMessage(message: String, duration: NSTimeInterval = 1.0) { 25 | var labelConfiguration = NotificationLabelConfiguration() 26 | labelConfiguration.backgroundColor = UIColor.redColor() 27 | labelConfiguration.scrollSpeed = 160.0 28 | 29 | var notificationCenterConfiguration = NotificationCenterConfiguration(baseWindow: UIApplication.sharedApplication().keyWindow!) 30 | notificationCenterConfiguration.level = UIWindowLevelStatusBar 31 | 32 | StatusBarNotificationCenter.showStatusBarNotificationWithMessage(message, forDuration: duration, withNotificationCenterConfiguration: notificationCenterConfiguration, andNotificationLabelConfiguration: labelConfiguration) 33 | } 34 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Share Manager/Service Provider/AlipayServiceProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlipayServiceProvider.swift 3 | // China 4 | // 5 | // Created by Shannon Wu on 12/1/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cupid 11 | 12 | class AlipayServiceProvider: ShareServiceProvider { 13 | var appKey: String 14 | private var shareCompletionHandler: ShareCompletionHandler? 15 | var oauthCompletionHandler: NetworkResponseHandler? 16 | 17 | lazy var alipayDelegate: APOpenAPIDelegate = { 18 | let delegate = AlipayDelegate() 19 | delegate.alipayServiceProvider = self 20 | return delegate 21 | }() 22 | 23 | init(appKey: String) { 24 | self.appKey = appKey 25 | APOpenAPI.registerApp(ShareManager.Alipay.appKey) 26 | } 27 | 28 | static var appInstalled: Bool { 29 | return APOpenAPI.isAPAppInstalled() && APOpenAPI.isAPAppSupportOpenApi() 30 | } 31 | 32 | static var canOAuth: Bool { 33 | return false 34 | } 35 | 36 | static func canShareContent(content: Content) -> Bool { 37 | return appInstalled 38 | } 39 | 40 | func canShareContent(content: Content) -> Bool { 41 | return AlipayServiceProvider.canShareContent(content) 42 | } 43 | 44 | func shareContent(content: Content, completionHandler: ShareCompletionHandler? = nil) throws { 45 | guard canShareContent(content) else { 46 | throw ShareError.ContentCannotShare 47 | } 48 | 49 | self.shareCompletionHandler = completionHandler 50 | 51 | let message = APMediaMessage() 52 | message.title = content.title ?? "" 53 | message.desc = content.description ?? "" 54 | if let thumbnail = content.thumbnail { 55 | message.thumbData = UIImagePNGRepresentation(thumbnail) 56 | } 57 | 58 | func share(message : APMediaMessage) { 59 | let req = APSendMessageToAPReq() 60 | req.message = message 61 | if !APOpenAPI.sendReq(req) { 62 | completionHandler?(succeed: false) 63 | } 64 | } 65 | 66 | guard let media = content.media else { 67 | return share(message) 68 | } 69 | switch media { 70 | case .URL(let url): 71 | let obj = APShareWebObject() 72 | obj.wepageUrl = url.absoluteString 73 | message.mediaObject = obj 74 | case .Image(let image): 75 | let obj = APShareImageObject() 76 | obj.imageData = UIImagePNGRepresentation(image) 77 | message.mediaObject = obj 78 | case .Audio(audioURL: let audioURL, linkURL: let linkURL): 79 | let obj = APShareTextObject() 80 | obj.text = "\(audioURL) \(linkURL ?? "")" 81 | message.mediaObject = obj 82 | case .Video(let URL): 83 | let obj = APShareTextObject() 84 | obj.text = "\(URL)" 85 | message.mediaObject = obj 86 | } 87 | share(message) 88 | } 89 | 90 | 91 | func OAuth(completionHandler: NetworkResponseHandler) throws { 92 | throw ShareError.NotSupported 93 | } 94 | 95 | func handleOpenURL(URL: NSURL) -> Bool? { 96 | if URL.scheme.hasPrefix("ap") { 97 | return APOpenAPI.handleOpenURL(URL, delegate: alipayDelegate) 98 | } else { 99 | return nil 100 | } 101 | } 102 | 103 | } 104 | 105 | class AlipayDelegate: NSObject, APOpenAPIDelegate { 106 | var alipayServiceProvider: AlipayServiceProvider? 107 | 108 | func onReq(req: APBaseReq!) {} 109 | 110 | func onResp(resp: APBaseResp!) { 111 | alipayServiceProvider?.shareCompletionHandler?(succeed: (resp.errCode == APSuccess.rawValue)) 112 | } 113 | } -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Share Manager/ShareManager+Type.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShareManager+Type.swift 3 | // China 4 | // 5 | // Created by Shannon Wu on 12/1/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cupid 11 | 12 | /// Extension to manager differenct third party platforms. 13 | extension ShareManager { 14 | /// Share Destination 15 | enum Destination { 16 | case Weibo 17 | case QQ 18 | case Alipay 19 | case WechatTimeline 20 | case WechatSession 21 | case Pasteboard 22 | 23 | var serviceProviderType: ShareServiceProvider.Type { 24 | let serviceProvierClass: ShareServiceProvider.Type 25 | switch self { 26 | case .QQ: 27 | serviceProvierClass = QQServiceProvider.self 28 | case .Alipay: 29 | serviceProvierClass = AlipayServiceProvider.self 30 | case .WechatSession, 31 | .WechatTimeline: 32 | serviceProvierClass = WeChatServiceProvier.self 33 | 34 | case .Weibo: 35 | serviceProvierClass = WeiboServiceProvier.self 36 | 37 | case .Pasteboard: 38 | serviceProvierClass = PasteboardServiceProvider.self 39 | } 40 | 41 | return serviceProvierClass 42 | } 43 | 44 | var serviceProvider: ShareServiceProvider { 45 | let serviceProvider: ShareServiceProvider 46 | 47 | switch self { 48 | case .QQ: 49 | serviceProvider = QQServiceProvider(appID: ShareManager.QQ.appID, destination: .Friends) 50 | 51 | case .Alipay: 52 | serviceProvider = AlipayServiceProvider(appKey: ShareManager.Alipay.appKey) 53 | 54 | case .WechatSession: 55 | serviceProvider = WeChatServiceProvier(appID: ShareManager.Wechat.appID, appKey: Wechat.appKey, destination: .Session) 56 | 57 | case .WechatTimeline: 58 | serviceProvider = WeChatServiceProvier(appID: ShareManager.Wechat.appID, appKey: Wechat.appKey, destination: .Timeline) 59 | 60 | case .Weibo: 61 | serviceProvider = WeiboServiceProvier(appID: ShareManager.Weibo.appID, appKey: ShareManager.Weibo.appKey, redirectURL: ShareManager.Weibo.redirectURL) 62 | 63 | case .Pasteboard: 64 | serviceProvider = PasteboardServiceProvider() 65 | } 66 | 67 | return serviceProvider 68 | } 69 | } 70 | 71 | enum OAuthType { 72 | case QQ 73 | case Wechat 74 | case Weibo 75 | 76 | var serviceProvider: ShareServiceProvider { 77 | switch self { 78 | case .QQ: 79 | return Destination.QQ.serviceProvider 80 | case .Wechat: 81 | return Destination.WechatTimeline.serviceProvider 82 | case .Weibo: 83 | return Destination.Weibo.serviceProvider 84 | } 85 | } 86 | 87 | var serviceProviderType: ShareServiceProvider.Type { 88 | switch self { 89 | case .QQ: 90 | return Destination.QQ.serviceProviderType 91 | case .Wechat: 92 | return Destination.WechatTimeline.serviceProviderType 93 | case .Weibo: 94 | return Destination.Weibo.serviceProviderType 95 | } 96 | } 97 | } 98 | 99 | enum OAuthResponse { 100 | case Tencent(accessToken: String, openID: String) 101 | case Weibo(accessToken: String) 102 | } 103 | 104 | /// QQ configuration information 105 | struct QQ { 106 | static let appID = "1104999026" 107 | static let appKey = "BLeQhlFDONwyoKs2" 108 | } 109 | 110 | /// Weibo Configuration information 111 | struct Weibo { 112 | static let appID = "4005151997" 113 | static let appKey = "11c2cb5cd5a3c347744a8eb808ede882" 114 | static let redirectURL = "http://weibo.com/igenuis/home?wvr=5&lf=reg" 115 | } 116 | 117 | /// Wechat configuration information 118 | struct Wechat { 119 | static let appID = "wx4868b35061f87885" 120 | static let appKey = "64020361b8ec4c99936c0e3999a9f249" 121 | } 122 | 123 | /// Alipay configuration information 124 | struct Alipay { 125 | static let appKey = "xxxxxxxxxxxxxxxx" 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Share Manager/ShareManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShareManager.swift 3 | // China 4 | // 5 | // Created by Shannon Wu on 12/1/15. 6 | // Copyright © 2015 36Kr. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cupid 11 | 12 | /// For reference outside this file 13 | typealias ShareContent = Content 14 | 15 | struct ShareManager { 16 | /// Check if possible to share content to the desired destination 17 | static func canShareContent(content: ShareContent, to destination: Destination) -> Bool { 18 | 19 | return destination.serviceProviderType.canShareContent(content) 20 | } 21 | 22 | static func canOAuthTo(type type: OAuthType) -> Bool { 23 | return type.serviceProviderType.canOAuth 24 | } 25 | 26 | static func shareContent(content: ShareContent, 27 | to destination: Destination, 28 | succeed successBlock: (Void -> Void), 29 | failed failBlock: (Void -> Void)) { 30 | do { 31 | try Cupid.shareContent(content, serviceProvider: destination.serviceProvider) { 32 | succeed in 33 | if succeed { 34 | successBlock() 35 | } else { 36 | failBlock() 37 | } 38 | } 39 | } 40 | catch _ { 41 | // maybe log error in formal environment 42 | failBlock() 43 | } 44 | } 45 | 46 | static func OAuth(type: OAuthType, 47 | succeeded successBlock: ((response:OAuthResponse) -> Void), 48 | failed failBlock: ((errorMessage:String?) -> Void)) { 49 | 50 | let serviceProvider = type.serviceProvider 51 | do { 52 | try Cupid.OAuth(serviceProvider) { 53 | (OAuthInfo, URLResonse, error) -> Void in 54 | switch type { 55 | case .QQ, 56 | .Wechat: 57 | if let accessToken = OAuthInfo?["access_token"] as? String, 58 | openID = OAuthInfo?["openid"] as? String { 59 | successBlock(response: .Tencent(accessToken: accessToken, openID: openID)) 60 | } else { 61 | failBlock(errorMessage: NSLocalizedString("第三方认证登陆失败,请重试或登陆36氪账号", comment: "")) 62 | } 63 | case .Weibo: 64 | if let accessToken = OAuthInfo?["accessToken"] as? String { 65 | successBlock(response: .Weibo(accessToken: accessToken)) 66 | } else { 67 | failBlock(errorMessage: NSLocalizedString("微博认证登陆失败,请重试或登陆36氪账号", comment: "")) 68 | } 69 | } 70 | } 71 | } 72 | catch _ { 73 | // maybe log error in formal environment 74 | failBlock(errorMessage: NSLocalizedString("第三方认证登陆失败,请重试或登陆36氪账号", comment: "")) 75 | } 76 | } 77 | 78 | static func handleOpenURL(url: NSURL) -> Bool? { 79 | return Cupid.handleOpenURL(url) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Share Manager/ShareSDK/AlipaySDK/APOpenAPI.h: -------------------------------------------------------------------------------- 1 | // 2 | // APOpenAPI.h 3 | // 所有API接口 4 | // 5 | // Created by Alipay on 15-4-15. 6 | // Copyright (c) 2015年 Alipay. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "APOpenAPIObject.h" 11 | 12 | #pragma mark - APOpenAPIDelegate 13 | 14 | /*! @brief 接收并处理来自支付宝终端程序的事件消息 15 | * 16 | * 接收并处理来自支付宝终端程序的事件消息,期间支付宝界面会切换到第三方应用程序。 17 | * APOpenAPIDelegate 会在handleOpenURL:delegate:中使用并触发。 18 | */ 19 | @protocol APOpenAPIDelegate 20 | @optional 21 | 22 | /*! @brief 收到一个来自支付宝的请求,第三方应用程序处理完后调用sendResp向支付宝发送结果 23 | * 24 | * 收到一个来自支付宝的请求,异步处理完成后必须调用sendResp发送处理结果给支付宝。 25 | * @param req 具体请求内容 26 | */ 27 | -(void) onReq:(APBaseReq*)req; 28 | 29 | 30 | 31 | /*! @brief 发送一个sendReq后,收到支付宝的回应 32 | * 33 | * 收到一个来自支付宝的处理结果。调用一次sendReq后会收到onResp。 34 | * @param resp具体的回应内容 35 | */ 36 | -(void) onResp:(APBaseResp*)resp; 37 | 38 | @end 39 | 40 | #pragma mark - APOpenAPI 41 | 42 | /*! @brief 支付宝API接口函数类 43 | * 44 | * 该类封装了支付宝终端SDK的所有接口 45 | */ 46 | @interface APOpenAPI : NSObject 47 | 48 | 49 | /*! @brief APOpenAPI的成员函数,向支付宝终端程序注册第三方应用。 50 | * 51 | * 需要在每次启动第三方应用程序时调用。第一次调用后,会在支付宝的可用应用列表中出现。 52 | * iOS7及以上系统需要调起一次支付宝才会出现在支付宝的可用应用列表中。 53 | * @attention 请保证在主线程中调用此函数 54 | * @param appid 支付宝开发者ID 55 | * @return 成功返回YES,失败返回NO。 56 | */ 57 | +(BOOL) registerApp:(NSString *)appid; 58 | 59 | 60 | 61 | /*! @brief APOpenAPI的成员函数,向支付宝终端程序注册第三方应用。 62 | * 63 | * 需要在每次启动第三方应用程序时调用。第一次调用后,会在支付宝的可用应用列表中出现。 64 | * @see registerApp 65 | * @param appid 支付宝开发者ID 66 | * @param appdesc 应用附加信息,长度不超过1024字节 67 | * @return 成功返回YES,失败返回NO。 68 | */ 69 | +(BOOL) registerApp:(NSString *)appid withDescription:(NSString *)appdesc; 70 | 71 | 72 | 73 | /*! @brief 处理支付宝通过URL启动App时传递的数据 74 | * 75 | * 需要在 application:openURL:sourceApplication:annotation:或者application:handleOpenURL中调用。 76 | * @param url 支付宝启动第三方应用时传递过来的URL 77 | * @param delegate APOpenAPIDelegate对象,用来接收支付宝触发的消息。 78 | * @return 成功返回YES,失败返回NO。 79 | */ 80 | +(BOOL) handleOpenURL:(NSURL *) url delegate:(id) delegate; 81 | 82 | 83 | 84 | /*! @brief 检查支付宝是否已被用户安装 85 | * 86 | * @return 支付宝已安装返回YES,未安装返回NO。 87 | */ 88 | +(BOOL) isAPAppInstalled; 89 | 90 | 91 | 92 | /*! @brief 判断当前支付宝的版本是否支持OpenApi 93 | * 94 | * @return 支持返回YES,不支持返回NO。 95 | */ 96 | +(BOOL) isAPAppSupportOpenApi; 97 | 98 | 99 | 100 | /*! @brief 获取支付宝的itunes安装地址 101 | * 102 | * @return 支付宝的安装地址字符串。 103 | */ 104 | +(NSString *) getAPAppInstallUrl; 105 | 106 | 107 | 108 | /*! @brief 获取当前支付宝SDK的版本号 109 | * 110 | * @return 返回当前支付宝SDK的版本号 111 | */ 112 | +(NSString *) getApiVersion; 113 | 114 | 115 | 116 | /*! @brief 打开支付宝 117 | * 118 | * @return 成功返回YES,失败返回NO。 119 | */ 120 | +(BOOL) openAPApp; 121 | 122 | 123 | /*! @brief 发送请求到支付宝,等待支付宝返回onResp 124 | * 125 | * 函数调用后,会切换到支付宝的界面。第三方应用程序等待支付宝返回onResp。支付宝在异步处理完成后一定会调用onResp。支持以下类型 126 | * @param req 具体的发送请求,在调用函数后,请自己释放。 127 | * @return 成功返回YES,失败返回NO。 128 | */ 129 | +(BOOL) sendReq:(APBaseReq*)req; 130 | 131 | 132 | /*! @brief 收到支付宝onReq的请求,发送对应的应答给支付宝,并切换到支付宝界面 133 | * 134 | * 函数调用后,会切换到支付宝的界面。第三方应用程序收到支付宝onReq的请求,异步处理该请求,完成后必须调用该函数。可能发送的相应有 135 | * @param resp 具体的应答内容,调用函数后,请自己释放 136 | * @return 成功返回YES,失败返回NO。 137 | */ 138 | +(BOOL) sendResp:(APBaseResp*)resp; 139 | 140 | @end 141 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Share Manager/ShareSDK/AlipaySDK/APOpenAPIObject.h: -------------------------------------------------------------------------------- 1 | // 2 | // APOpenAPIObject.h 3 | // API对象,包含所有接口和对象数据定义 4 | // 5 | // Created by Alipay on 15-4-15. 6 | // Copyright (c) 2015年 Alipay. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | // 错误码 13 | typedef enum { 14 | APSuccess = 0, // 成功 15 | APErrCodeCommon = -1, // 通用错误 16 | APErrCodeUserCancel = -2, // 用户取消 17 | APErrCodeSentFail = -3, // 发送失败 18 | APErrCodeAuthDeny = -4, // 授权失败 19 | APErrCodeUnsupport = -5, // 不支持 20 | }APErrorCode; 21 | 22 | // 分享场景 23 | typedef enum { 24 | APSceneSession = 0, // 会话 25 | }APScene; 26 | 27 | #pragma mark - APBaseReq 28 | 29 | /*! @brief 该类为支付宝SDK所有请求类的基类 30 | * 31 | */ 32 | @interface APBaseReq : NSObject 33 | /** 请求类型 */ 34 | @property (nonatomic, assign) int type; 35 | /** AppID,发送请求时第三方程序必须填写 */ 36 | @property (nonatomic, retain) NSString* openID; 37 | @end 38 | 39 | #pragma mark - APBaseResp 40 | 41 | /*! @brief 该类为SDK所有响应类的基类 42 | * 43 | */ 44 | @interface APBaseResp : NSObject 45 | /** 错误码 */ 46 | @property (nonatomic, assign) int errCode; 47 | /** 错误提示字符串 */ 48 | @property (nonatomic, strong) NSString *errStr; 49 | /** 响应类型 */ 50 | @property (nonatomic, assign) int type; 51 | @property (nonatomic, strong) NSString *openID; 52 | @end 53 | 54 | #pragma mark - 发送消息到支付宝 55 | /*! @brief 第三方程序发送消息至支付宝终端程序的消息结构体 56 | * 57 | * 第三方程序向支付宝发送信息需要传入SendMessageToAPReq结构体,调用该方法后,支付宝处理完信息会向第三方程序发送一个处理结果。 58 | * @see SendMessageToAPReq 59 | */ 60 | @class APMediaMessage; 61 | @interface APSendMessageToAPReq : APBaseReq 62 | // 发送消息的多媒体内容 63 | @property (nonatomic, strong) APMediaMessage* message; 64 | @property (nonatomic, assign) APScene scene; 65 | @end 66 | 67 | /*! @brief 支付宝终端向第三方程序返回的SendMessageToAPReq处理结果。 68 | * 69 | * 第三方程序向支付宝终端发送SendMessageToAPReq后,支付宝发送回来的处理结果,该结果用SendMessageToAPResp表示。 70 | */ 71 | @interface APSendMessageToAPResp : APBaseResp 72 | 73 | @end 74 | 75 | #pragma mark - APMediaMessage 76 | /*! @brief 多媒体消息结构体 77 | * 78 | * 用于支付宝终端和第三方程序之间传递消息的多媒体消息内容 79 | */ 80 | @interface APMediaMessage : NSObject 81 | // 标题 82 | @property (nonatomic, strong) NSString *title; 83 | // 描述内容 84 | @property (nonatomic, strong) NSString *desc; 85 | // 缩略图数据 86 | @property (nonatomic, strong) NSData *thumbData; 87 | @property (nonatomic, strong) NSString *thumbUrl; 88 | // 多媒体对象 89 | @property (nonatomic, strong) id mediaObject; 90 | @end 91 | 92 | // 文本 93 | @interface APShareTextObject : NSObject 94 | @property (nonatomic, strong) NSString *text; 95 | @end; 96 | 97 | // 图片 98 | @interface APShareImageObject : NSObject 99 | @property (nonatomic, strong) NSData *imageData; 100 | @property (nonatomic, strong) NSString *imageUrl; 101 | @end; 102 | 103 | // 网页 104 | @interface APShareWebObject : NSObject 105 | @property (nonatomic, strong) NSString *wepageUrl; 106 | @end; 107 | -------------------------------------------------------------------------------- /CupidDemo/CupidDemo/Share Manager/ShareSDK/AlipaySDK/libAPOpenSdk.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/CupidDemo/CupidDemo/Share Manager/ShareSDK/AlipaySDK/libAPOpenSdk.a -------------------------------------------------------------------------------- /CupidDemo/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - StatusBarNotificationCenter (1.1.3) 3 | 4 | DEPENDENCIES: 5 | - StatusBarNotificationCenter 6 | 7 | SPEC CHECKSUMS: 8 | StatusBarNotificationCenter: f788bd9192bf5f7a40117f96a173a138be739823 9 | 10 | COCOAPODS: 0.39.0 11 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - StatusBarNotificationCenter (1.1.3) 3 | 4 | DEPENDENCIES: 5 | - StatusBarNotificationCenter 6 | 7 | SPEC CHECKSUMS: 8 | StatusBarNotificationCenter: f788bd9192bf5f7a40117f96a173a138be739823 9 | 10 | COCOAPODS: 0.39.0 11 | -------------------------------------------------------------------------------- /CupidDemo/Pods/StatusBarNotificationCenter/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Shannon Wu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /CupidDemo/Pods/StatusBarNotificationCenter/Pod/Configuration/NotificationCenterConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationCenterConfiguration.swift 3 | // StatusBarNotification 4 | // 5 | // Created by Shannon Wu on 9/18/15. 6 | // Copyright © 2015 Shannon Wu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | * Customize the overall configuration information of the notification, most of the property's default value is OK for most circumstance, but you can customize it if you want 13 | */ 14 | public struct NotificationCenterConfiguration { 15 | /// The window below the notification window, you must set this property, or the notification will not work correctly 16 | var baseWindow: UIWindow 17 | /// The style of the notification, default to status bar notification 18 | public var style = StatusBarNotificationCenter.Style.StatusBar 19 | /// The animation type of the notification, default to overlay 20 | public var animationType = StatusBarNotificationCenter.AnimationType.Overlay 21 | /// The animate in direction of the notification, default to top 22 | public var animateInDirection = StatusBarNotificationCenter.AnimationDirection.Top 23 | /// The animate out direction of the notification, default to top 24 | public var animateOutDirection = StatusBarNotificationCenter.AnimationDirection.Top 25 | /// Whether the user can tap on the notification to dismiss the notification, default to true 26 | public var dismissible = true 27 | /// The animate in time of the notification 28 | public var animateInLength: NSTimeInterval = 0.25 29 | /// The animate out time of the notification 30 | public var animateOutLength: NSTimeInterval = 0.25 31 | /// The height of the notification view, if you want to use a custom height, set the style of the notification to custom, or it will use the status bar and navigation bar height 32 | public var height: CGFloat = 0 33 | /// If the status bar is hidden, if it is hidden, the hight of the navigation style notification height is the height of the navigation bar, default to false 34 | public var statusBarIsHidden: Bool = false 35 | /// The height of the navigation bar, default to 44.0 points 36 | public var navigationBarHeight: CGFloat = 44.0 37 | /// Should allow the user to interact with the content outside the notification 38 | public var userInteractionEnabled = true 39 | /// The window level of the notification window 40 | public var level: CGFloat = UIWindowLevelNormal 41 | 42 | /** 43 | Initializer 44 | 45 | - parameter baseWindow: the base window of the notification 46 | 47 | - returns: a default NotificationCenterConfiguration instance 48 | */ 49 | public init(baseWindow: UIWindow) { 50 | self.baseWindow = baseWindow 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /CupidDemo/Pods/StatusBarNotificationCenter/Pod/Configuration/NotificationLabelConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationLabelConfiguration.swift 3 | // StatusBarNotification 4 | // 5 | // Created by Shannon Wu on 9/18/15. 6 | // Copyright © 2015 Shannon Wu. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | * If you use the default label to show the notification, you should send a customized configuration struct, the dufault implementation is a nonscrollabel label, with one line to show the information 13 | */ 14 | public struct NotificationLabelConfiguration { 15 | /// if the label should scroll the content, default to false 16 | public var scrollabel = true 17 | /// If you set the scrollabel property to true, you can use this property to customize the scroll delay, default delay is 1 second 18 | public var scrollDelay: NSTimeInterval = 1.0 19 | /// If you set the scrollabel property to true, you can use this property to customize the scroll speed, default speed is 40 points per second 20 | public var scrollSpeed: CGFloat = 40.0 21 | /// Set the padding of the message label, default to 10.0 points 22 | public var padding: CGFloat = 10.0 23 | /// if the label should be multiline implementation, default to false 24 | public var multiline = false 25 | /// The background color of the notification view, default to black color 26 | public var backgroundColor = UIColor.blackColor() 27 | /// The text color of the notification view, default to white color 28 | public var textColor = UIColor.whiteColor() 29 | /// The font of the notification label, defalt to a system font of size 14.0, if you pass the attributed string, this property will be ignored 30 | public var font = UIFont.systemFontOfSize(StatusBarNotificationCenter.defaultMessageLabelFontSize) 31 | /// this property is not nil, the label will use the attributed string to show the message 32 | public var attributedText: NSAttributedString? = nil 33 | 34 | /** 35 | Init a new default notification label configuration 36 | 37 | - returns: a new default notification label configuration 38 | */ 39 | public init() { 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /CupidDemo/Pods/StatusBarNotificationCenter/Pod/Notification Center/BaseScrollLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseScrollLabel.swift 3 | // StatusBarNotification 4 | // 5 | // Created by Shannon Wu on 9/17/15. 6 | // Copyright © 2015 Shannon Wu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// A subclass of UILabel to implement the scrolling trait 12 | class BaseScrollLabel: UILabel { 13 | //MARK: - Properties 14 | 15 | var messageImage = UIImageView() 16 | var scrollable: Bool = true 17 | var scrollSpeed: CGFloat = 40.0 18 | var scrollDelay: NSTimeInterval = 1.0 19 | var padding: CGFloat = 10.0 20 | 21 | var fullWidth: CGFloat { 22 | guard let message = text else { return 0 } 23 | return (message as NSString).sizeWithAttributes([NSFontAttributeName : font]).width 24 | } 25 | 26 | var scrollOffset: CGFloat { 27 | if (numberOfLines != 1) || !scrollable { return 0 } 28 | let insetRect = CGRectInset(bounds, padding, 0) 29 | return max(0, fullWidth - CGRectGetWidth(insetRect)) 30 | } 31 | 32 | var scrollTime: NSTimeInterval { 33 | return (scrollOffset > 0) ? NSTimeInterval(scrollOffset / scrollSpeed) + scrollDelay : 0 34 | } 35 | 36 | override func drawTextInRect(rect: CGRect) { 37 | var rect = rect 38 | if scrollOffset > 0 { 39 | rect.size.width = fullWidth + padding * 2 40 | 41 | UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale) 42 | super.drawTextInRect(rect) 43 | messageImage.image = UIGraphicsGetImageFromCurrentImageContext() 44 | UIGraphicsEndImageContext() 45 | 46 | messageImage.sizeToFit() 47 | addSubview(messageImage) 48 | 49 | UIView.animateWithDuration(scrollTime - scrollDelay, delay: scrollDelay, options: [.BeginFromCurrentState, .CurveEaseInOut], animations: { () -> Void in 50 | self.messageImage.transform = CGAffineTransformMakeTranslation(-self.scrollOffset, 0) 51 | }, completion: { (finished) -> Void in 52 | // 53 | }) 54 | 55 | } else { 56 | messageImage.removeFromSuperview() 57 | super.drawTextInRect(CGRectInset(rect, padding, padding)) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /CupidDemo/Pods/StatusBarNotificationCenter/Pod/Notification Center/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.swift 3 | // StatusBarNotification 4 | // 5 | // Created by Shannon Wu on 9/17/15. 6 | // Copyright © 2015 Shannon Wu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// The view controller object of the notification window 12 | class BaseViewController: UIViewController { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /CupidDemo/Pods/StatusBarNotificationCenter/Pod/Notification Center/BaseWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseWindow.swift 3 | // StatusBarNotification 4 | // 5 | // Created by Shannon Wu on 9/17/15. 6 | // Copyright © 2015 Shannon Wu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// The window of the notification center 12 | class BaseWindow: UIWindow { 13 | weak var notificationCenter: StatusBarNotificationCenter! 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | 18 | backgroundColor = UIColor.clearColor() 19 | userInteractionEnabled = true 20 | hidden = true 21 | windowLevel = UIWindowLevelNormal 22 | rootViewController = BaseViewController() 23 | } 24 | 25 | required init?(coder aDecoder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | 29 | func resetRootViewController() { 30 | if let rootViewController = rootViewController as? BaseViewController { 31 | for view in rootViewController.view.subviews { 32 | view.removeFromSuperview() 33 | } 34 | } 35 | } 36 | 37 | override func hitTest(pt: CGPoint, withEvent event: UIEvent?) -> UIView? { 38 | if pt.y > 0 && pt.y < (notificationCenter.internalnotificationViewHeight) { 39 | return super.hitTest(pt, withEvent: event) 40 | } 41 | return nil 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CupidDemo/Pods/StatusBarNotificationCenter/Pod/Notification Center/StatusBarNotificationCenter+Logic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatusBarNotificationCenter+Public Interface.swift 3 | // StatusBarNotification 4 | // 5 | // Created by Shannon Wu on 9/17/15. 6 | // Copyright © 2015 Shannon Wu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - Notification center logic 12 | extension StatusBarNotificationCenter { 13 | //MARK: - Notification Management 14 | /** 15 | Show a status bar notification with custom view, and dismiss it automatically 16 | 17 | - parameter view: the custom view of the notification 18 | - parameter duration: the showing time of the notification 19 | - parameter notificationCenterConfiguration: the notification configuration 20 | */ 21 | public class func showStatusBarNotificationWithView(view: UIView, forDuration duration: NSTimeInterval, withNotificationCenterConfiguration notificationCenterConfiguration: NotificationCenterConfiguration) { 22 | let notification = Notification(view: view, message:nil, notificationCenterConfiguration: notificationCenterConfiguration, viewSource: .CustomView, notificationLabelConfiguration: nil, duration: duration, completionHandler: nil) 23 | StatusBarNotificationCenter.center.processNotification(notification) 24 | } 25 | 26 | /** 27 | Show a status bar notification with custom view, you can pass a completion hander to be invoked when the notification is showed, but you must dismiss it yourself 28 | 29 | - parameter view: the custom view of the notification 30 | - parameter notificationCenterConfiguration: the notification configuration 31 | - parameter completionHandler: the block to be invoked when the notification is being showed 32 | */ 33 | public class func showStatusBarNotificationWithView(view: UIView, withNotificationCenterConfiguration notificationCenterConfiguration: NotificationCenterConfiguration, whenComplete completionHandler: (Void -> Void)? = nil) { 34 | let notification = Notification(view: view, message:nil, notificationCenterConfiguration: notificationCenterConfiguration, viewSource: .CustomView, notificationLabelConfiguration: nil, duration: nil, completionHandler: completionHandler) 35 | StatusBarNotificationCenter.center.processNotification(notification) 36 | } 37 | 38 | /** 39 | Show a status bar notification with a label, and dismiss it automatically 40 | 41 | - parameter message: the message to be showed 42 | - parameter duration: the showing time of the notification 43 | - parameter notificationCenterConfiguration: the notification configuration 44 | - parameter andNotificationLabelConfiguration: the label configuration 45 | */ 46 | public class func showStatusBarNotificationWithMessage(message: String?, forDuration duration: NSTimeInterval, withNotificationCenterConfiguration notificationCenterConfiguration: NotificationCenterConfiguration, andNotificationLabelConfiguration notificationLabelConfiguration: NotificationLabelConfiguration) { 47 | let notification = Notification(view: nil, message:message, notificationCenterConfiguration: notificationCenterConfiguration, viewSource: .Label, notificationLabelConfiguration: notificationLabelConfiguration, duration: duration, completionHandler: nil) 48 | StatusBarNotificationCenter.center.processNotification(notification) 49 | } 50 | 51 | /** 52 | Show a status bar notification with a label, you can pass a completion hander to be invoked when the notification is showed, but you must dismiss it yourself 53 | 54 | - parameter message: the message to be showed 55 | - parameter notificationCenterConfiguration: the notification configuration 56 | - parameter andNotificationLabelConfiguration: the label configuration 57 | - parameter completionHandler: the block to be invoked when the notification is being showed 58 | */ 59 | public class func showStatusBarNotificationWithMessage(message: String?, withNotificationCenterConfiguration notificationCenterConfiguration: NotificationCenterConfiguration, andNotificationLabelConfiguration notificationLabelConfiguration: NotificationLabelConfiguration, whenComplete completionHandler: (Void -> Void)? = nil) { 60 | let notification = Notification(view: nil, message:message, notificationCenterConfiguration: notificationCenterConfiguration, viewSource: .Label, notificationLabelConfiguration: notificationLabelConfiguration, duration: nil, completionHandler: completionHandler) 61 | StatusBarNotificationCenter.center.processNotification(notification) 62 | } 63 | 64 | /** 65 | A helper method to precess the notification 66 | 67 | - parameter notification: the notification to be processed 68 | */ 69 | func processNotification(notification: Notification) { 70 | dispatch_async(notificationQ) { () -> Void in 71 | StatusBarNotificationCenter.center.notifications.append(notification) 72 | StatusBarNotificationCenter.center.showNotification() 73 | } 74 | } 75 | 76 | /** 77 | This is the hub of all notifications, just use a semaphore to manage the showing process 78 | */ 79 | func showNotification() { 80 | if (dispatch_semaphore_wait(self.notificationSemaphore, DISPATCH_TIME_FOREVER) == 0) { 81 | dispatch_sync(dispatch_get_main_queue(), { () -> Void in 82 | if self.notifications.count > 0 { 83 | let currentNotification = self.notifications.removeFirst() 84 | 85 | self.notificationCenterConfiguration = currentNotification.notificationCenterConfiguration 86 | self.notificationLabelConfiguration = currentNotification.notificationLabelConfiguration 87 | self.notificationWindow.resetRootViewController() 88 | switch currentNotification.viewSource { 89 | case .CustomView: 90 | self.viewSource = .CustomView 91 | 92 | if let duration = currentNotification.duration { 93 | self.showStatusBarNotificationWithView(currentNotification.view, forDuration: duration) 94 | } else { 95 | self.showStatusBarNotificationWithView(currentNotification.view, completion: currentNotification.completionHandler) 96 | } 97 | case .Label: 98 | self.viewSource = .Label 99 | 100 | if let duration = currentNotification.duration { 101 | self.showStatusBarNotificationWithMessage(currentNotification.message, forDuration: duration) 102 | } else { 103 | self.showStatusBarNotificationWithMessage(currentNotification.message, completion: currentNotification.completionHandler) 104 | } 105 | } 106 | } else { 107 | return 108 | } 109 | }) 110 | } 111 | } 112 | 113 | func showStatusBarNotificationWithMessage(message: String?,completion: (() -> Void)?) { 114 | 115 | self.createMessageLabelWithMessage(message) 116 | self.createSnapshotView() 117 | notificationWindow.windowLevel = notificationCenterConfiguration.level 118 | 119 | if let messageLabel = self.messageLabel { 120 | self.notificationWindow.rootViewController?.view.addSubview(messageLabel) 121 | self.notificationWindow.rootViewController?.view.bringSubviewToFront(messageLabel) 122 | } 123 | self.notificationWindow.hidden = false 124 | 125 | NSNotificationCenter.defaultCenter().addObserver(self, selector: "screenOrientationChanged", name: UIApplicationDidChangeStatusBarOrientationNotification, object: nil) 126 | 127 | UIView.animateWithDuration(self.animateInLength, animations: { () -> Void in 128 | self.animateInFrameChange() 129 | }, completion: { (finished) -> Void in 130 | let delayInSeconds = self.messageLabel.scrollTime 131 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), { () -> Void in 132 | if let completion = completion { 133 | completion() 134 | } 135 | }) 136 | }) 137 | 138 | } 139 | 140 | func showStatusBarNotificationWithMessage(message: String?, forDuration duration: NSTimeInterval) { 141 | self.showStatusBarNotificationWithMessage(message) { () -> Void in 142 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(Double(duration) * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), { () -> Void in 143 | self.dismissNotification() 144 | }) 145 | } 146 | } 147 | 148 | func showStatusBarNotificationWithView(view: UIView, completion: (Void -> Void)?) { 149 | 150 | self.notificationWindow.hidden = false 151 | 152 | self.customView = view 153 | self.notificationWindow.rootViewController?.view.addSubview(view) 154 | self.notificationWindow.rootViewController?.view.bringSubviewToFront(view) 155 | self.createSnapshotView() 156 | 157 | NSNotificationCenter.defaultCenter().addObserver(self, selector: "screenOrientationChanged", name: UIApplicationDidChangeStatusBarOrientationNotification, object: nil) 158 | 159 | UIView.animateWithDuration(self.animateInLength, animations: { () -> Void in 160 | self.animateInFrameChange() 161 | }, completion: { (finished) -> Void in 162 | completion?() 163 | }) 164 | 165 | } 166 | 167 | func showStatusBarNotificationWithView(view: UIView, forDuration duration: NSTimeInterval) { 168 | self.showStatusBarNotificationWithView(view) { () -> Void in 169 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(Double(duration) * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), { () -> Void in 170 | self.dismissNotification() 171 | }) 172 | } 173 | } 174 | 175 | /** 176 | Dismiss the currently showing notification, and you can pass a completion handler, if you want to dismiss the currently showing notification 177 | 178 | - parameter completion: completion handler to invoke when the dismiss procedure finished 179 | */ 180 | public class func dismissNotificationWithCompletion(completion: (() -> Void)?) { 181 | StatusBarNotificationCenter.center.dismissNotificationWithCompletion(completion) 182 | } 183 | 184 | func dismissNotificationWithCompletion(completion: (() -> Void)?) { 185 | 186 | self.middleFrameChange() 187 | UIView.animateWithDuration(self.animateOutLength, animations: { () -> Void in 188 | self.animateOutFrameChange() 189 | }, completion: { (finished) -> Void in 190 | self.notificationWindow.hidden = true 191 | self.messageLabel = nil 192 | self.snapshotView = nil 193 | self.customView = nil 194 | 195 | self.notificationLabelConfiguration = nil 196 | self.notificationCenterConfiguration = nil 197 | 198 | NSNotificationCenter.defaultCenter().removeObserver(self, name: UIApplicationDidChangeStatusBarOrientationNotification, object: nil) 199 | 200 | if let completion = completion { 201 | completion() 202 | } 203 | 204 | dispatch_semaphore_signal(self.notificationSemaphore) 205 | }) 206 | } 207 | 208 | func dismissNotification() { 209 | if self.dismissible { 210 | self.dismissNotificationWithCompletion(nil) 211 | } 212 | } 213 | 214 | 215 | //MARK: - Handle Change 216 | 217 | func notificationTapped(tapGesture: UITapGestureRecognizer) { 218 | dismissNotification() 219 | } 220 | 221 | func screenOrientationChanged() { 222 | switch viewSource { 223 | case .Label: 224 | messageLabel?.frame = notificationViewFrame 225 | case .CustomView: 226 | customView?.frame = notificationViewFrame 227 | } 228 | } 229 | 230 | //MARK: - Helper animation state 231 | 232 | func animateInFrameChange() { 233 | let view: UIView? 234 | switch viewSource { 235 | case .Label: 236 | view = messageLabel 237 | case .CustomView: 238 | view = customView 239 | } 240 | 241 | view?.frame = notificationViewFrame 242 | 243 | switch animateInDirection { 244 | case .Top: 245 | snapshotView?.frame = notificationViewBottomFrame 246 | case .Left: 247 | snapshotView?.frame = notificationViewRightFrame 248 | case .Right: 249 | snapshotView?.frame = notificationViewLeftFrame 250 | case .Bottom: 251 | snapshotView?.frame = notificationViewTopFrame 252 | } 253 | } 254 | 255 | func middleFrameChange() { 256 | switch animateOutDirection { 257 | case .Top: 258 | snapshotView?.frame = notificationViewBottomFrame 259 | case .Left: 260 | snapshotView?.frame = notificationViewRightFrame 261 | case .Right: 262 | snapshotView?.frame = notificationViewLeftFrame 263 | case .Bottom: 264 | snapshotView?.frame = notificationViewTopFrame 265 | } 266 | } 267 | 268 | func animateOutFrameChange() { 269 | let view: UIView? 270 | switch viewSource { 271 | case .Label: 272 | view = messageLabel 273 | case .CustomView: 274 | view = customView 275 | } 276 | 277 | snapshotView?.frame = notificationViewFrame 278 | switch animateOutDirection { 279 | case .Top: 280 | view?.frame = notificationViewTopFrame 281 | case .Left: 282 | view?.frame = notificationViewLeftFrame 283 | case .Right: 284 | view?.frame = notificationViewRightFrame 285 | case .Bottom: 286 | view?.frame = notificationViewBottomFrame 287 | } 288 | } 289 | 290 | //MARK: - Helper view creation 291 | 292 | func createMessageLabelWithMessage(message: String?) { 293 | messageLabel = BaseScrollLabel() 294 | messageLabel?.text = message 295 | messageLabel?.textAlignment = .Center 296 | messageLabel?.font = messageLabelFont 297 | messageLabel?.numberOfLines = messageLabelMultiline ? 0 : 1 298 | messageLabel?.textColor = messageLabelTextColor 299 | messageLabel?.backgroundColor = messageLabelBackgroundColor 300 | messageLabel?.scrollable = messageLabelScrollable 301 | messageLabel?.scrollSpeed = messageLabelScrollSpeed 302 | messageLabel?.scrollDelay = messageLabelScrollDelay 303 | messageLabel?.padding = messageLabelPadding 304 | if let messageLabelAttributedText = messageLabelAttributedText { 305 | messageLabel?.attributedText = messageLabelAttributedText 306 | } 307 | setupNotificationView(messageLabel) 308 | } 309 | 310 | func setupNotificationView(view: UIView?) { 311 | view?.clipsToBounds = true 312 | view?.userInteractionEnabled = true 313 | 314 | let tapGesture = UITapGestureRecognizer(target: self, action: "notificationTapped:") 315 | view?.addGestureRecognizer(tapGesture) 316 | 317 | switch animateInDirection { 318 | case .Top: 319 | view?.frame = notificationViewTopFrame 320 | case .Left: 321 | view?.frame = notificationViewLeftFrame 322 | case .Right: 323 | view?.frame = notificationViewRightFrame 324 | case .Bottom: 325 | view?.frame = notificationViewBottomFrame 326 | } 327 | } 328 | 329 | func createSnapshotView() { 330 | if animationType != .Replace { return } 331 | 332 | snapshotView = UIView(frame: notificationViewFrame) 333 | snapshotView!.clipsToBounds = true 334 | snapshotView!.backgroundColor = UIColor.clearColor() 335 | 336 | let view = baseWindow.snapshotViewAfterScreenUpdates(true) 337 | snapshotView!.addSubview(view) 338 | notificationWindow.rootViewController?.view.addSubview(snapshotView!) 339 | notificationWindow.rootViewController?.view.sendSubviewToBack(snapshotView!) 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /CupidDemo/Pods/StatusBarNotificationCenter/Pod/Notification Center/StatusBarNotificationCenter+Type.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatusBarNotification+Type.swift 3 | // StatusBarNotification 4 | // 5 | // Created by Shannon Wu on 9/15/15. 6 | // Copyright © 2015 Shannon Wu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - Notification center types 12 | extension StatusBarNotificationCenter { 13 | //MARK: - Types 14 | 15 | /** 16 | Notification Style 17 | - StatusBar: Covers the status bar portion of the screen 18 | - NavigationBar: Covers the status bar and navigation bar portions of the screen 19 | */ 20 | public enum Style: NSInteger { 21 | /** 22 | * Covers the statusbar portion of the screen, if the status bar is hidden, cover the status bar height of the screen 23 | */ 24 | case StatusBar 25 | /** 26 | * Covers the status bar and navigation bar portion of the screen 27 | */ 28 | case NavigationBar 29 | /** 30 | * Cover part of the screen, based on the height of the configuration 31 | */ 32 | case Custom 33 | } 34 | 35 | /** 36 | The direction of animation for the notification 37 | 38 | - Top: Animate in from the top or animate out to the top 39 | - Bottom: Animate in from the bottom or animate out to the bottom 40 | - Left: Animate in from the left or animate out to the left 41 | - Right: Animate in from the right or animate out to the right 42 | */ 43 | public enum AnimationDirection: NSInteger { 44 | /** 45 | * Animate in from the top or animate out to the top 46 | */ 47 | case Top 48 | /** 49 | * Animate in from the bottom or animate out to the bottom 50 | */ 51 | case Bottom 52 | /** 53 | * Animate in from the left or animate out to the left 54 | */ 55 | case Left 56 | /** 57 | * Animate in from the right or animate out to the right 58 | */ 59 | case Right 60 | } 61 | 62 | /** 63 | Determines whether the notification moves the existing content out of the way or simply overlays it. 64 | 65 | - Replace: Moves existing content out of the way 66 | - Overlay: Ovelays existing content 67 | */ 68 | public enum AnimationType: NSInteger { 69 | /** 70 | * Moves existing content out of the way 71 | */ 72 | case Replace 73 | /** 74 | * Ovelays existing content 75 | */ 76 | case Overlay 77 | } 78 | 79 | /** 80 | The view source of the notification 81 | 82 | - CustomView: Use a custom view to show the notification 83 | - Label: Use the default label to show the notification 84 | */ 85 | enum ViewSource: NSInteger { 86 | /** 87 | * Use a custom view to show the notification 88 | */ 89 | case CustomView 90 | /** 91 | * Use the default label to show the notification 92 | */ 93 | case Label 94 | } 95 | 96 | /** 97 | * This is the base element of the notification queue 98 | */ 99 | struct Notification { 100 | /// This is the notification center configuration object 101 | let notificationCenterConfiguration: NotificationCenterConfiguration 102 | /// This the notification label configuration object 103 | let notificationLabelConfiguration: NotificationLabelConfiguration? 104 | /// This is the duration of the notification 105 | let duration: NSTimeInterval? 106 | /// The view source of the notification 107 | let viewSource: ViewSource 108 | /// The view of the notification, if the view source is a custom view 109 | let view: UIView! 110 | /// The completion handler to be called when the show process is done 111 | let completionHandler: (Void -> Void)? 112 | /// The message of the notification, if the view source is a label 113 | let message: String! 114 | 115 | 116 | /** 117 | Init a new notification object 118 | 119 | - parameter notificationCenterConfiguration: This is the notification center configuration object 120 | - parameter viewSource: The view source of the notification 121 | - parameter notificationLabelConfiguration: This the notification label configuration object 122 | - parameter duration: This is the duration of the notification 123 | - parameter completionHandler: The completion handler to be called when the show process is done 124 | 125 | - returns: a newly initialtiated notification 126 | */ 127 | init(view: UIView?, message: String?, notificationCenterConfiguration: NotificationCenterConfiguration, viewSource: ViewSource, notificationLabelConfiguration:NotificationLabelConfiguration?, duration: NSTimeInterval?, completionHandler: (Void -> Void)?) { 128 | self.view = view 129 | self.message = message 130 | self.notificationCenterConfiguration = notificationCenterConfiguration 131 | self.notificationLabelConfiguration = notificationLabelConfiguration 132 | self.duration = duration 133 | self.viewSource = viewSource 134 | self.completionHandler = completionHandler 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /CupidDemo/Pods/StatusBarNotificationCenter/Pod/Notification Center/StatusBarNotificationCenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // StatusBarNotification 4 | // 5 | // Created by Shannon Wu on 9/16/15. 6 | // Copyright © 2015 Shannon Wu. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// This is the base class of the notification center object, mainly to define properties 12 | public class StatusBarNotificationCenter: NSObject { 13 | //MARK: - Initializers 14 | 15 | private override init() { 16 | super.init() 17 | 18 | notificationWindow.notificationCenter = self 19 | } 20 | 21 | /// The single status bar notification center 22 | class var center: StatusBarNotificationCenter { 23 | struct SingletonWrapper { 24 | static let singleton = StatusBarNotificationCenter() 25 | } 26 | return SingletonWrapper.singleton 27 | } 28 | 29 | //MARK: - Snapshoot View 30 | var snapshotView: UIView? 31 | 32 | //MARK: Message Label 33 | var messageLabel: BaseScrollLabel! 34 | var messageLabelScrollable: Bool { 35 | if let notificationLabelConfiguration = notificationLabelConfiguration { 36 | return notificationLabelConfiguration.scrollabel 37 | } else { 38 | fatalError("Cannot reach this branch") 39 | } 40 | } 41 | var messageLabelBackgroundColor: UIColor { 42 | if let notificationLabelConfiguration = notificationLabelConfiguration { 43 | return notificationLabelConfiguration.backgroundColor 44 | } else { 45 | fatalError("Cannot reach this branch") 46 | } 47 | } 48 | var messageLabelTextColor: UIColor { 49 | if let notificationLabelConfiguration = notificationLabelConfiguration { 50 | return notificationLabelConfiguration.textColor 51 | } else { 52 | fatalError("Cannot reach this branch") 53 | } 54 | } 55 | var messageLabelMultiline: Bool { 56 | if let notificationLabelConfiguration = notificationLabelConfiguration { 57 | return notificationLabelConfiguration.multiline 58 | } else { 59 | fatalError("Cannot reach this branch") 60 | } 61 | } 62 | var messageLabelFont: UIFont { 63 | if let notificationLabelConfiguration = notificationLabelConfiguration { 64 | return notificationLabelConfiguration.font 65 | } else { 66 | fatalError("Cannot reach this branch") 67 | } 68 | } 69 | var messageLabelScrollSpeed: CGFloat { 70 | if let notificationLabelConfiguration = notificationLabelConfiguration { 71 | return notificationLabelConfiguration.scrollSpeed 72 | } else { 73 | fatalError("Cannot reach this branch") 74 | } 75 | } 76 | var messageLabelScrollDelay: NSTimeInterval { 77 | if let notificationLabelConfiguration = notificationLabelConfiguration { 78 | return notificationLabelConfiguration.scrollDelay 79 | } else { 80 | fatalError("Cannot reach this branch") 81 | } 82 | } 83 | var messageLabelPadding: CGFloat { 84 | if let notificationLabelConfiguration = notificationLabelConfiguration { 85 | return notificationLabelConfiguration.padding 86 | } else { 87 | fatalError("Cannot reach this branch") 88 | } 89 | } 90 | var messageLabelAttributedText: NSAttributedString? { 91 | if let notificationLabelConfiguration = notificationLabelConfiguration { 92 | return notificationLabelConfiguration.attributedText 93 | } else { 94 | fatalError("Cannot reach this branch") 95 | } 96 | } 97 | /// The default message label font size, default to 14.0 points, if you want to use the default label and want to change its font size, you can use this property to configure it 98 | static var defaultMessageLabelFontSize: CGFloat = 14 99 | 100 | //MARK: Notification View 101 | var notificationViewWidth: CGFloat { 102 | return baseWindow.bounds.width 103 | } 104 | 105 | var notificationViewHeight: CGFloat { 106 | if let notificationCenterConfiguration = notificationCenterConfiguration { 107 | return notificationCenterConfiguration.height 108 | } else { 109 | fatalError("Cannot reach this branch") 110 | } 111 | } 112 | var internalnotificationViewHeight: CGFloat { 113 | switch notificationStyle { 114 | case .StatusBar: 115 | return statusBarHeight 116 | case .NavigationBar: 117 | if statusBarIsHidden { 118 | return navigationBarHeight 119 | } else { 120 | return statusBarHeight + navigationBarHeight 121 | } 122 | case .Custom: 123 | return notificationViewHeight 124 | } 125 | } 126 | 127 | var notificationViewFrame: CGRect { 128 | return CGRectMake(0, 0, notificationViewWidth, internalnotificationViewHeight) 129 | } 130 | var notificationViewTopFrame: CGRect { 131 | return CGRectMake(0, -internalnotificationViewHeight, notificationViewWidth, internalnotificationViewHeight) 132 | } 133 | var notificationViewLeftFrame: CGRect { 134 | return CGRectMake(-notificationViewWidth, 0, notificationViewWidth, internalnotificationViewHeight) 135 | } 136 | var notificationViewRightFrame: CGRect { 137 | return CGRectMake(notificationViewWidth, 0, notificationViewWidth, internalnotificationViewHeight) 138 | } 139 | var notificationViewBottomFrame: CGRect { 140 | return CGRectMake(0, internalnotificationViewHeight, notificationViewWidth, 0) 141 | } 142 | 143 | //MARK: Custom View 144 | var viewSource: ViewSource = .Label 145 | var customView: UIView? { 146 | didSet { 147 | if customView != nil { 148 | setupNotificationView(customView) 149 | } 150 | } 151 | } 152 | 153 | //MARK: Status Bar 154 | var statusBarIsHidden: Bool = false 155 | var statusBarHeight: CGFloat { 156 | return 20.0 157 | } 158 | 159 | //MARK: Navigation Bar 160 | var navigationBarHeight: CGFloat { 161 | if let notificationCenterConfiguration = notificationCenterConfiguration { 162 | return notificationCenterConfiguration.navigationBarHeight 163 | } else { 164 | fatalError("Cannot reach this branch") 165 | } 166 | } 167 | 168 | //MARK: Animation Parameter 169 | var notificationStyle: Style { 170 | if let notificationCenterConfiguration = notificationCenterConfiguration { 171 | return notificationCenterConfiguration.style 172 | } else { 173 | fatalError("Cannot reach this branch") 174 | } 175 | } 176 | var animateInDirection: AnimationDirection { 177 | if let notificationCenterConfiguration = notificationCenterConfiguration { 178 | return notificationCenterConfiguration.animateInDirection 179 | } else { 180 | fatalError("Cannot reach this branch") 181 | } 182 | } 183 | var animateOutDirection: AnimationDirection { 184 | if let notificationCenterConfiguration = notificationCenterConfiguration { 185 | return notificationCenterConfiguration.animateOutDirection 186 | } else { 187 | fatalError("Cannot reach this branch") 188 | } 189 | } 190 | var animationType: AnimationType { 191 | if let notificationCenterConfiguration = notificationCenterConfiguration { 192 | return notificationCenterConfiguration.animationType 193 | } else { 194 | fatalError("Cannot reach this branch") 195 | } 196 | } 197 | 198 | //MARK: - Window 199 | let notificationWindow = BaseWindow(frame: UIScreen.mainScreen().bounds) 200 | 201 | var baseWindow: UIWindow { 202 | if let notificationCenterConfiguration = notificationCenterConfiguration { 203 | return notificationCenterConfiguration.baseWindow 204 | } else { 205 | fatalError("Cannot reach this branch") 206 | } 207 | } 208 | 209 | //MARK: - Notification 210 | var notificationCenterConfiguration: NotificationCenterConfiguration! 211 | var notificationLabelConfiguration: NotificationLabelConfiguration! 212 | 213 | var dismissible: Bool { 214 | if let notificationCenterConfiguration = notificationCenterConfiguration { 215 | return notificationCenterConfiguration.dismissible 216 | } else { 217 | return false 218 | } 219 | } 220 | var animateInLength: NSTimeInterval { 221 | if let notificationCenterConfiguration = notificationCenterConfiguration { 222 | return notificationCenterConfiguration.animateInLength 223 | } else { 224 | fatalError("Cannot reach this branch") 225 | } 226 | } 227 | var animateOutLength: NSTimeInterval { 228 | if let notificationCenterConfiguration = notificationCenterConfiguration { 229 | return notificationCenterConfiguration.animateOutLength 230 | } else { 231 | fatalError("Cannot reach this branch") 232 | } 233 | } 234 | 235 | //MARK: - Notification Queue Management 236 | /// A notification array 237 | var notifications = [Notification]() 238 | /// Create a notification Queue to track the notifications 239 | let notificationQ = dispatch_queue_create("notificationQueue", DISPATCH_QUEUE_SERIAL) 240 | /// Create a semaphore to show the notification in a one-after one basis 241 | let notificationSemaphore = dispatch_semaphore_create(1) 242 | 243 | 244 | //MARK: - User Interaction 245 | 246 | var userInteractionEnabled: Bool { 247 | return notificationCenterConfiguration.userInteractionEnabled 248 | } 249 | } -------------------------------------------------------------------------------- /CupidDemo/Pods/StatusBarNotificationCenter/README.md: -------------------------------------------------------------------------------- 1 | # StatusBarNotificationCenter 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/StatusBarNotificationCenter.svg?style=flat)](http://cocoapods.org/pods/StatusBarNotificationCenter) 4 | [![License](https://img.shields.io/cocoapods/l/StatusBarNotificationCenter.svg?style=flat)](http://cocoapods.org/pods/StatusBarNotificationCenter) 5 | [![Platform](https://img.shields.io/cocoapods/p/StatusBarNotificationCenter.svg?style=flat)](http://cocoapods.org/pods/StatusBarNotificationCenter) 6 | 7 | ![screenshot](screenshots/screenshoot.gif) 8 | 9 | You may also want to check this short [Youtube Video] (https://youtu.be/Qk2vhrBAyps?list=PLy5xoZi6fpzJ0z2xtlqL9Hz86IrpZuksG) to see what it can generally do 10 | 11 | You can also check this short [Youtube Video] (https://youtu.be/vtMWcWVtxZ8) to learn more about its implementation detail 12 | 13 | `StatusBarNotificationCenter` is a library that can be used in your application to show customized status bar notification. 14 | 15 | **NOTE:**During out software development, we want to find a library that can show notification from the status bar, This project learned many thought from the popular [`CWStatusBarNotification`](https://github.com/cezarywojcik/CWStatusBarNotification) library, but with much cleaner code implementation(in my own option) and fully written in **Swift 2.0**, and more extendable, and also it comes with more customisation options, and support multitasking and split view comes with iOS9+ . You can check it if you want to find a custom status bar notification library. 16 | 17 | ## Key Feature 18 | 1. Support split view of iPad Air and iPad Pro 19 | 2. Support concurrency, with version 1.1.0, you can just test it with the demo application 20 | 3. Highly customizable with a simple architecture, just a main class with a few class methods 21 | 4. Fully documented 22 | 23 | **Now, you can let the users interact with the app during the notification is showing by setting the userInteractionEnabled flag of thee StatusBarNotificationCenter configuration, and you can check the latest commit to say how easy it is to add this functionality** 24 | 25 | ## A few words 26 | This library is just a center class with a window and a view controller, and the center maintains an notification queue, I think our main aim is to build a stable and maintainable architecture, I want to add as many functionality as well, but I really like simple architecture, so there has to be some balance, and I am a programmer just for a few months, so there maybe some thing that maybe not appropriate, if you have some advice, please contant me with my email, you can easily add your custom view to this library, if you have something great to share, please open an issue or submit a pull request, thanks for your support. 27 | 28 | ## Example 29 | 30 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 31 | 32 | This example is best run with an iPad air or iPad Pro in portrait mode, and you can test the split view 33 | 34 | ##Requirements 35 | Because the demonstration code is written with UIStackView, so you need Xcode 7.0+ and iOS 9.0+ to run the demo, But I think this library can be used with devices with system version 7.0+, because the API is rather basic, and you can modify the source code with little effort to accompany with your minimum deployment target. 36 | 37 | Works on iPhone and iPad 38 | 39 | ## Installation 40 | 41 | StatusBarNotificationCenter is available through [CocoaPods](http://cocoapods.org). To install 42 | it, simply add the following line to your Podfile, because this is written in Swift 2.0, you may also need to insert `use_frameworks!` in your Podfile: 43 | 44 | ```ruby 45 | pod "StatusBarNotificationCenter" 46 | ``` 47 | ##Usage 48 | 49 | First, you need to import the `StatusBarNotificationCenter` framework 50 | 51 | Second, you must supply a `NotificationCenterConfiguration` object, the default implementation is below: 52 | 53 | ```swift 54 | /** 55 | * Customize the overall configuration information of the notification, most of the property's default value is OK for most circumstance, but you can customize it if you want 56 | */ 57 | public struct NotificationCenterConfiguration { 58 | /// The window below the notification window, you must set this property, or the notification will not work correctly 59 | var baseWindow: UIWindow 60 | /// The style of the notification, default to status bar notification 61 | public var style = StatusBarNotificationCenter.Style.StatusBar 62 | /// The animation type of the notification, default to overlay 63 | public var animationType = StatusBarNotificationCenter.AnimationType.Overlay 64 | /// The animate in direction of the notification, default to top 65 | public var animateInDirection = StatusBarNotificationCenter.AnimationDirection.Top 66 | /// The animate out direction of the notification, default to top 67 | public var animateOutDirection = StatusBarNotificationCenter.AnimationDirection.Top 68 | /// Whether the user can tap on the notification to dismiss the notification, default to true 69 | public var dismissible = true 70 | /// The animate in time of the notification 71 | public var animateInLength: NSTimeInterval = 0.25 72 | /// The animate out time of the notification 73 | public var animateOutLength: NSTimeInterval = 0.25 74 | /// The height of the notification view, if you want to use a custom height, set the style of the notification to custom, or it will use the status bar and navigation bar height 75 | public var height: CGFloat = 0 76 | /// If the status bar is hidden, if it is hidden, the hight of the navigation style notification height is the height of the navigation bar, default to false 77 | public var statusBarIsHidden: Bool = false 78 | /// The height of the navigation bar, default to 44.0 points 79 | public var navigationBarHeight: CGFloat = 44.0 80 | /// Should allow the user to interact with the content outside the notification 81 | public var userInteractionEnabled = true 82 | 83 | /** 84 | Initializer 85 | 86 | - parameter baseWindow: the base window of the notification 87 | 88 | - returns: a default NotificationCenterConfiguration instance 89 | */ 90 | public init(baseWindow: UIWindow) { 91 | self.baseWindow = baseWindow 92 | } 93 | } 94 | ``` 95 | 96 | **NOTE:** when you want to show a notification, you must supply the baseWindow of this notification, this property is mainly used to capture a snapshot of the your applications underlining view, the ordinary window will be your application's view's window: 97 | ```swift 98 | view.window! 99 | ``` 100 | 101 | If you want to show the notification with a custom view, you can just can the class method with the configuration object 102 | ```swift 103 | StatusBarNotificationCenter.showStatusBarNotificationWithView(view, forDuration: NSTimeInterval(durationSlider.value), withNotificationCenterConfiguration: notificationCenterConfiguration) 104 | ``` 105 | this method will display the notification, and last for a time of your specification, and if your configuration object's dismissible property is true, the user can dismiss the notification with a tap on the status bar, if you want to display the notification, and dismiss it manually, you can call the method below instead of this one 106 | ```swift 107 | func showStatusBarNotificationWithView(view: UIView, withNotificationCenterConfiguration notificationCenterConfiguration: NotificationCenterConfiguration, whenComplete completionHandler: Void -> Void) 108 | ``` 109 | and you can supply a completion hander which will be called when the display complete, but you must dismiss it yourself, and if your configuration object's dismissible property is true, the user can dismiss the notification with a tap on the status bar 110 | 111 | if you want to display the notification with the string value, you must also pass a `NotificationLabelConfiguration` object, the default implementation of this object is below: 112 | ```swift 113 | /** 114 | * If you use the default label to show the notification, you should send a customized configuration struct, the dufault implementation is a non-scrollabel label, with one line to show the information 115 | */ 116 | public struct NotificationLabelConfiguration { 117 | /// if the label should scroll the content, default to false 118 | public var scrollabel = true 119 | /// If you set the scrollable property to true, you can use this property to customize the scroll delay, default delay is 1 second 120 | public var scrollDelay: NSTimeInterval = 1.0 121 | /// If you set the scrollabel property to true, you can use this property to customize the scroll speed, default speed is 40 points per second 122 | public var scrollSpeed: CGFloat = 40.0 123 | /// Set the padding of the message label, default to 10.0 points 124 | public var padding: CGFloat = 10.0 125 | /// if the label should be multiline implementation, default to false 126 | public var multiline = false 127 | /// The background color of the notification view, default to black color 128 | public var backgroundColor = UIColor.blackColor() 129 | /// The text color of the notification view, default to white color 130 | public var textColor = UIColor.whiteColor() 131 | /// The font of the notification label, default to a system font of size 14.0, if you pass the attributed string, this property will be ignored 132 | public var font = UIFont.systemFontOfSize(StatusBarNotificationCenter.defaultMessageLabelFontSize) 133 | /// this property is not nil, the label will use the attributed string to show the message 134 | public var attributedText: NSAttributedString? = nil 135 | 136 | /** 137 | Init a new default notification label configuration 138 | 139 | - returns: a new default notification label configuration 140 | */ 141 | public init() { 142 | 143 | } 144 | } 145 | ``` 146 | The configuration is rather obvious, you can call the following method to invoke the notification 147 | ```swift 148 | StatusBarNotificationCenter.showStatusBarNotificationWithMessage(notificationTextField.text, forDuration: NSTimeInterval(durationSlider.value), withNotificationCenterConfiguration: notificationCenterConfiguration, andNotificationLabelConfiguration: notificationLabelConfiguration) 149 | ``` 150 | and there is also a similar method below, the usage is similar to the notification with a custom view 151 | ```swift 152 | func showStatusBarNotificationWithMessage(message: String?, withNotificationCenterConfiguration notificationCenterConfiguration: NotificationCenterConfiguration, andNotificationLabelConfiguration notificationLabelConfiguration: NotificationLabelConfiguration, whenComplete completionHandler: Void -> Void) 153 | ``` 154 | ### Additional Remarks 155 | This library is not yet fully tested, if you find some bugs, please submit an issue; especially, this library is not multi thread tested, though I think with some modification, it will work correctly, if you find, please don't hesitate to tell me, or submit a pull request. 156 | 157 | And really a big thanks to the work of [`CWStatusBarNotification`](https://github.com/cezarywojcik/CWStatusBarNotification) , I think you can compare the two library if you decide to use a status bar notification, originally, I want to modify that repo, but I think write a library from the beginning is a better idea. 158 | 159 | ## Author 160 | 161 | Shannon Wu 162 | you can contact me by [Email](inatu@icloud.com), or [twitter](https://twitter.com/inatu_) or [Weibo](http://weibo.com/igenuis/profile?rightmod=1&wvr=6&mod=personinfo) 163 | 164 | ## License 165 | 166 | StatusBarNotificationCenter is available under the MIT license. See the LICENSE file for more info. 167 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/Pods-CupidDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/Pods-CupidDemo/Pods-CupidDemo-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## StatusBarNotificationCenter 5 | 6 | Copyright (c) 2015 Shannon Wu 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | Generated by CocoaPods - http://cocoapods.org 27 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/Pods-CupidDemo/Pods-CupidDemo-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Copyright (c) 2015 Shannon Wu <inatu@icloud.com> 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | Title 38 | StatusBarNotificationCenter 39 | Type 40 | PSGroupSpecifier 41 | 42 | 43 | FooterText 44 | Generated by CocoaPods - http://cocoapods.org 45 | Title 46 | 47 | Type 48 | PSGroupSpecifier 49 | 50 | 51 | StringsTable 52 | Acknowledgements 53 | Title 54 | Acknowledgements 55 | 56 | 57 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/Pods-CupidDemo/Pods-CupidDemo-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_CupidDemo : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_CupidDemo 5 | @end 6 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/Pods-CupidDemo/Pods-CupidDemo-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 6 | 7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 8 | 9 | install_framework() 10 | { 11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 12 | local source="${BUILT_PRODUCTS_DIR}/$1" 13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 15 | elif [ -r "$1" ]; then 16 | local source="$1" 17 | fi 18 | 19 | local destination="${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 20 | 21 | if [ -L "${source}" ]; then 22 | echo "Symlinked..." 23 | source="$(readlink "${source}")" 24 | fi 25 | 26 | # use filter instead of exclude so missing patterns dont' throw errors 27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 29 | 30 | local basename 31 | basename="$(basename -s .framework "$1")" 32 | binary="${destination}/${basename}.framework/${basename}" 33 | if ! [ -r "$binary" ]; then 34 | binary="${destination}/${basename}" 35 | fi 36 | 37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 39 | strip_invalid_archs "$binary" 40 | fi 41 | 42 | # Resign the code if required by the build settings to avoid unstable apps 43 | code_sign_if_enabled "${destination}/$(basename "$1")" 44 | 45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 47 | local swift_runtime_libs 48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]}) 49 | for lib in $swift_runtime_libs; do 50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 52 | code_sign_if_enabled "${destination}/${lib}" 53 | done 54 | fi 55 | } 56 | 57 | # Signs a framework with the provided identity 58 | code_sign_if_enabled() { 59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 60 | # Use the current code_sign_identitiy 61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 62 | echo "/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --preserve-metadata=identifier,entitlements \"$1\"" 63 | /usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} --preserve-metadata=identifier,entitlements "$1" 64 | fi 65 | } 66 | 67 | # Strip invalid architectures 68 | strip_invalid_archs() { 69 | binary="$1" 70 | # Get architectures for current file 71 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)" 72 | stripped="" 73 | for arch in $archs; do 74 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then 75 | # Strip non-valid architectures in-place 76 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1 77 | stripped="$stripped $arch" 78 | fi 79 | done 80 | if [[ "$stripped" ]]; then 81 | echo "Stripped $binary of architectures:$stripped" 82 | fi 83 | } 84 | 85 | 86 | if [[ "$CONFIGURATION" == "Debug" ]]; then 87 | install_framework "Pods-CupidDemo/StatusBarNotificationCenter.framework" 88 | fi 89 | if [[ "$CONFIGURATION" == "Release" ]]; then 90 | install_framework "Pods-CupidDemo/StatusBarNotificationCenter.framework" 91 | fi 92 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/Pods-CupidDemo/Pods-CupidDemo-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 5 | 6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt 7 | > "$RESOURCES_TO_COPY" 8 | 9 | XCASSET_FILES=() 10 | 11 | realpath() { 12 | DIRECTORY="$(cd "${1%/*}" && pwd)" 13 | FILENAME="${1##*/}" 14 | echo "$DIRECTORY/$FILENAME" 15 | } 16 | 17 | install_resource() 18 | { 19 | case $1 in 20 | *.storyboard) 21 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc ${PODS_ROOT}/$1 --sdk ${SDKROOT}" 22 | ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .storyboard`.storyboardc" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" 23 | ;; 24 | *.xib) 25 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib ${PODS_ROOT}/$1 --sdk ${SDKROOT}" 26 | ibtool --reference-external-strings-file --errors --warnings --notices --output-format human-readable-text --compile "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$1\" .xib`.nib" "${PODS_ROOT}/$1" --sdk "${SDKROOT}" 27 | ;; 28 | *.framework) 29 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 30 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 31 | echo "rsync -av ${PODS_ROOT}/$1 ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 32 | rsync -av "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 33 | ;; 34 | *.xcdatamodel) 35 | echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1"`.mom\"" 36 | xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodel`.mom" 37 | ;; 38 | *.xcdatamodeld) 39 | echo "xcrun momc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd\"" 40 | xcrun momc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcdatamodeld`.momd" 41 | ;; 42 | *.xcmappingmodel) 43 | echo "xcrun mapc \"${PODS_ROOT}/$1\" \"${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm\"" 44 | xcrun mapc "${PODS_ROOT}/$1" "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$1" .xcmappingmodel`.cdm" 45 | ;; 46 | *.xcassets) 47 | ABSOLUTE_XCASSET_FILE=$(realpath "${PODS_ROOT}/$1") 48 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE") 49 | ;; 50 | /*) 51 | echo "$1" 52 | echo "$1" >> "$RESOURCES_TO_COPY" 53 | ;; 54 | *) 55 | echo "${PODS_ROOT}/$1" 56 | echo "${PODS_ROOT}/$1" >> "$RESOURCES_TO_COPY" 57 | ;; 58 | esac 59 | } 60 | 61 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 62 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 63 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then 64 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 65 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 66 | fi 67 | rm -f "$RESOURCES_TO_COPY" 68 | 69 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ] 70 | then 71 | case "${TARGETED_DEVICE_FAMILY}" in 72 | 1,2) 73 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone" 74 | ;; 75 | 1) 76 | TARGET_DEVICE_ARGS="--target-device iphone" 77 | ;; 78 | 2) 79 | TARGET_DEVICE_ARGS="--target-device ipad" 80 | ;; 81 | *) 82 | TARGET_DEVICE_ARGS="--target-device mac" 83 | ;; 84 | esac 85 | 86 | # Find all other xcassets (this unfortunately includes those of path pods and other targets). 87 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d) 88 | while read line; do 89 | if [[ $line != "`realpath $PODS_ROOT`*" ]]; then 90 | XCASSET_FILES+=("$line") 91 | fi 92 | done <<<"$OTHER_XCASSETS" 93 | 94 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${IPHONEOS_DEPLOYMENT_TARGET}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" 95 | fi 96 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/Pods-CupidDemo/Pods-CupidDemo-umbrella.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | FOUNDATION_EXPORT double Pods_CupidDemoVersionNumber; 5 | FOUNDATION_EXPORT const unsigned char Pods_CupidDemoVersionString[]; 6 | 7 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/Pods-CupidDemo/Pods-CupidDemo.debug.xcconfig: -------------------------------------------------------------------------------- 1 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "$CONFIGURATION_BUILD_DIR/StatusBarNotificationCenter.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "StatusBarNotificationCenter" 6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 7 | PODS_FRAMEWORK_BUILD_PATH = $(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Pods-CupidDemo 8 | PODS_ROOT = ${SRCROOT}/Pods -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/Pods-CupidDemo/Pods-CupidDemo.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_CupidDemo { 2 | umbrella header "Pods-CupidDemo-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/Pods-CupidDemo/Pods-CupidDemo.release.xcconfig: -------------------------------------------------------------------------------- 1 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "$CONFIGURATION_BUILD_DIR/StatusBarNotificationCenter.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "StatusBarNotificationCenter" 6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 7 | PODS_FRAMEWORK_BUILD_PATH = $(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Pods-CupidDemo 8 | PODS_ROOT = ${SRCROOT}/Pods -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/StatusBarNotificationCenter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.1.3 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/StatusBarNotificationCenter/StatusBarNotificationCenter-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_StatusBarNotificationCenter : NSObject 3 | @end 4 | @implementation PodsDummy_StatusBarNotificationCenter 5 | @end 6 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/StatusBarNotificationCenter/StatusBarNotificationCenter-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/StatusBarNotificationCenter/StatusBarNotificationCenter-umbrella.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | FOUNDATION_EXPORT double StatusBarNotificationCenterVersionNumber; 5 | FOUNDATION_EXPORT const unsigned char StatusBarNotificationCenterVersionString[]; 6 | 7 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/StatusBarNotificationCenter/StatusBarNotificationCenter.modulemap: -------------------------------------------------------------------------------- 1 | framework module StatusBarNotificationCenter { 2 | umbrella header "StatusBarNotificationCenter-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /CupidDemo/Pods/Target Support Files/StatusBarNotificationCenter/StatusBarNotificationCenter.xcconfig: -------------------------------------------------------------------------------- 1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 2 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/StatusBarNotificationCenter" "${PODS_ROOT}/Headers/Public" 3 | OTHER_LDFLAGS = -framework "UIKit" 4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 5 | PODS_ROOT = ${SRCROOT} 6 | SKIP_INSTALL = YES -------------------------------------------------------------------------------- /CupidDemo/podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '8.0' 2 | use_frameworks! 3 | 4 | target 'CupidDemo' do 5 | pod "StatusBarNotificationCenter" 6 | end 7 | 8 | target 'CupidDemoTests' do 9 | 10 | end 11 | 12 | target 'CupidDemoUITests' do 13 | 14 | end 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 36Kr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |

5 | 6 | # Cupid 7 | 8 | Just like [openshare](https://github.com/100apps/openshare) and [MonkeyKing](https://github.com/nixzhu/MonkeyKing.git), Cupid helps you post messages or do OAuth to Social Networks. And more service providers is on the schedule. In fact, the network interface is the same as `MonkeyKing`. So why a new share SDK? 9 | 10 | 1. This library is coming with much more extensibility. 11 | 2. Most apps have their own network interface, so it is not necessary to maintain another network service instance. 12 | 3. You don't need to register at the app launch methods; I think that is a waste of memory, many people never share anything. 13 | 4. This library used many structs to construct the information channel, and use enums to construct information payload! No more singletons to reside in memory! 14 | 5. Protocols to extend custom functionality. 15 | 16 | You can check [this article in Chinese](http://www.jianshu.com/p/bfa456a40d5f) to learn more about share architecture in our coming new version client. 17 | 18 | **Not like demo of many library, this library's demo's logic is very similar to our architecture in our in programming new version client. The `MainViewController` is just like share sheet, though our client is coming with a much more beautiful interface. The `ShareManager` is our central hub for communication between the library and our app logic. The [NotificationManager](https://github.com/36Kr-Mobile/StatusBarNotificationCenter) is used for notification. So this demo does not show all the functionality of this library** 19 | 20 | **If you want to share to Alipay, you must configure the app id and the bundle id to match your register to Alipay. If you have any questions, feel free to contact me or open an issue.** 21 | 22 | ## Features 23 | 1. [x] Share content to QQ, WeChat, Weibo, Pocket, Paste board, Alipay. 24 | 2. [x] OAuth to QQ(support web OAuth), WeChat, Weibo(Support web OAuth), Pocket(Support web OAuth). 25 | 3. [x] Subclass `ShareActivity` to create custom share UIActivity. 26 | 4. [x] Create your own service provider to offer more functionality. 27 | 5. [x] Optionally use your own network framework to do OAuth and share. 28 | 6. [ ] Code refactoring to make the web OAuth interface more customizable. 29 | 7. [ ] Code refactoring to make this library more elegant. 30 | 31 | A quick peek of the demo, but just with the interface screenshot. Download it to try! 32 | 33 | ![screenshot](screenshots/animated.gif) 34 | 35 | 36 | ## Requirements 37 | 38 | Swift 2.0+, iOS 8.0+ 39 | 40 | ## Installation 41 | 42 | It's recommended to use CocoaPods or Carthage. 43 | 44 | ### [CocoaPods](http://cocoapods.org) 45 | 46 | To integrate Cupid into your Xcode project using CocoaPods, specify it in your `Podfile`: 47 | 48 | ```ruby 49 | platform :ios, '8.0' 50 | use_frameworks! 51 | 52 | pod 'Cupid', '~> 1.0' 53 | ``` 54 | 55 | Then, run the following command: 56 | 57 | ```bash 58 | $ pod install 59 | ``` 60 | 61 | You should open the `{Project}.xcworkspace` instead of the `{Project}.xcodeproj` after you installed anything from CocoaPods. 62 | 63 | ### [Carthage](https://github.com/Carthage/Carthage) 64 | 65 | To integrate MonkeyKing into your Xcode project using Carthage, specify it in your `Cartfile`: 66 | 67 | ```ogdl 68 | github "36Kr-Mobile/Cupid.git" 69 | ``` 70 | 71 | Then, run the following command to build the Cupid framework: 72 | 73 | ```bash 74 | $ carthage update 75 | ``` 76 | 77 | ## Usage 78 | 79 | You can check the Cupid Demo to learn its usage, and also I recommend you to read this article to learn how we integrate this library into our own production code base. 80 | 81 | ## Contact 82 | 83 | Shannon Wu 84 | 85 | Twitter: [@inatu_](https://twitter.com/inatu_) 86 | 87 | Weibo: [inatu](http://weibo.com/igenuis/profile?rightmod=1&wvr=6&mod=personinfo) 88 | 89 | E-mail: [inatu@icloud.com](mailto:inatu@icloud.com) 90 | 91 | ## Credits 92 | 93 | Great thanks to the creators and contributors of `MonkeyKing`, many methods are directly from it. 94 | 95 | ## License 96 | 97 | Cupid is available under the [MIT License][mitLink] license. See the LICENSE file for more info. 98 | [mitLink]:http://opensource.org/licenses/MIT 99 | -------------------------------------------------------------------------------- /screenshots/animated.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/36Kr-Mobile/Cupid/a21bf6ba10b48d8133a827ba1bce250fd215e7a6/screenshots/animated.gif --------------------------------------------------------------------------------