Skip to content

Commit

Permalink
First working version using YPImagePicker (#18)
Browse files Browse the repository at this point in the history
* First working version using YPImagePicker

* Some experiments

* Support for image upload and cover image upload in Article form

* Adds AlamofireImage + lots of optimizations

* Inline comments for readability

* Small image resize enhancement
  • Loading branch information
fdocr committed Dec 23, 2020
1 parent d3145f3 commit dc1e7f2
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 6 deletions.
11 changes: 11 additions & 0 deletions Example/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>Allow the Example app to take pictures</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Allow the Example app to select pictures</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
Expand Down
1 change: 1 addition & 0 deletions Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
webView.foremWebViewDelegate = self
// webView.load("http://localhost:3000")
webView.load("https://dev.to")
activityIndicator.startAnimating()

Expand Down
71 changes: 70 additions & 1 deletion ForemWebView.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 52;
objects = {

/* Begin PBXBuildFile section */
Expand All @@ -24,8 +24,14 @@
DC3DAE9725229FD2004DAC13 /* forem.dev-logged-in.html in Resources */ = {isa = PBXBuildFile; fileRef = DC3DAE9525229FD2004DAC13 /* forem.dev-logged-in.html */; };
DC6576432559CCDD00B5B46F /* ForemMediaManager+PodcastActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6576422559CCDC00B5B46F /* ForemMediaManager+PodcastActions.swift */; };
DC6576472559CCED00B5B46F /* ForemMediaManager+VideoActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC6576462559CCED00B5B46F /* ForemMediaManager+VideoActions.swift */; };
DC8AC9FF25850EF00012DFAC /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DC8AC9FE25850EF00012DFAC /* AlamofireImage */; };
DC9C221A2581824B00B0F801 /* YPImagePicker in Frameworks */ = {isa = PBXBuildFile; productRef = DC9C22192581824B00B0F801 /* YPImagePicker */; };
DC9EF35D2549E013003A1BE7 /* ForemUserData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC9EF35C2549E013003A1BE7 /* ForemUserData.swift */; };
DC9EF3612549FDD5003A1BE7 /* invertedImages.css in Resources */ = {isa = PBXBuildFile; fileRef = DC9EF3602549FDD5003A1BE7 /* invertedImages.css */; };
DCA40E542582C73100D463DC /* UIImage+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA40E532582C73100D463DC /* UIImage+Utilities.swift */; };
DCA40E552582C73100D463DC /* UIImage+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA40E532582C73100D463DC /* UIImage+Utilities.swift */; };
DCA40E562582C73100D463DC /* UIImage+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA40E532582C73100D463DC /* UIImage+Utilities.swift */; };
DCA40E772583C45E00D463DC /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = DCA40E762583C45E00D463DC /* Alamofire */; };
DCD7C211254A9B4D00FAFC1D /* forem.dev-logged-in-dark.html in Resources */ = {isa = PBXBuildFile; fileRef = DCD7C210254A9B4D00FAFC1D /* forem.dev-logged-in-dark.html */; };
DCD7C21F254A9EE600FAFC1D /* forem.dev-logged-in-pink.html in Resources */ = {isa = PBXBuildFile; fileRef = DCD7C21E254A9EE600FAFC1D /* forem.dev-logged-in-pink.html */; };
DCF27D5C255116EF0065770D /* ForemWebView+WKNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF27D5B255116EF0065770D /* ForemWebView+WKNavigationDelegate.swift */; };
Expand Down Expand Up @@ -91,6 +97,7 @@
DC6576462559CCED00B5B46F /* ForemMediaManager+VideoActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ForemMediaManager+VideoActions.swift"; sourceTree = "<group>"; };
DC9EF35C2549E013003A1BE7 /* ForemUserData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForemUserData.swift; sourceTree = "<group>"; };
DC9EF3602549FDD5003A1BE7 /* invertedImages.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = invertedImages.css; sourceTree = "<group>"; };
DCA40E532582C73100D463DC /* UIImage+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Utilities.swift"; sourceTree = "<group>"; };
DCD7C210254A9B4D00FAFC1D /* forem.dev-logged-in-dark.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "forem.dev-logged-in-dark.html"; sourceTree = "<group>"; };
DCD7C21E254A9EE600FAFC1D /* forem.dev-logged-in-pink.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "forem.dev-logged-in-pink.html"; sourceTree = "<group>"; };
DCF27D5B255116EF0065770D /* ForemWebView+WKNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ForemWebView+WKNavigationDelegate.swift"; sourceTree = "<group>"; };
Expand All @@ -106,6 +113,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
DC9C221A2581824B00B0F801 /* YPImagePicker in Frameworks */,
DCA40E772583C45E00D463DC /* Alamofire in Frameworks */,
DC8AC9FF25850EF00012DFAC /* AlamofireImage in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -157,6 +167,7 @@
DCF27D5B255116EF0065770D /* ForemWebView+WKNavigationDelegate.swift */,
DC9EF35C2549E013003A1BE7 /* ForemUserData.swift */,
DCF27D7725519F660065770D /* ForemInstanceMetadata.swift */,
DCA40E532582C73100D463DC /* UIImage+Utilities.swift */,
DC3DAE3825229D8C004DAC13 /* Info.plist */,
DC65764D2559CE1E00B5B46F /* Media Manager */,
DC65764A2559CDA900B5B46F /* JavaScript & CSS */,
Expand Down Expand Up @@ -249,6 +260,11 @@
dependencies = (
);
name = ForemWebView;
packageProductDependencies = (
DC9C22192581824B00B0F801 /* YPImagePicker */,
DCA40E762583C45E00D463DC /* Alamofire */,
DC8AC9FE25850EF00012DFAC /* AlamofireImage */,
);
productName = ForemWebView;
productReference = DC3DAE3425229D8C004DAC13 /* ForemWebView.framework */;
productType = "com.apple.product-type.framework";
Expand Down Expand Up @@ -321,6 +337,11 @@
Base,
);
mainGroup = DC3DAE2A25229D8C004DAC13;
packageReferences = (
DC9C22182581824B00B0F801 /* XCRemoteSwiftPackageReference "YPImagePicker" */,
DCA40E752583C45D00D463DC /* XCRemoteSwiftPackageReference "Alamofire" */,
DC8AC9FD25850EF00012DFAC /* XCRemoteSwiftPackageReference "AlamofireImage" */,
);
productRefGroup = DC3DAE3525229D8C004DAC13 /* Products */;
projectDirPath = "";
projectRoot = "";
Expand Down Expand Up @@ -395,6 +416,7 @@
DC9EF35D2549E013003A1BE7 /* ForemUserData.swift in Sources */,
DC3DAE4125229DA5004DAC13 /* ForemWebView.swift in Sources */,
DC3DAE4425229DBB004DAC13 /* ForemWebView+WKScriptMessageHandler.swift in Sources */,
DCA40E542582C73100D463DC /* UIImage+Utilities.swift in Sources */,
DCF27D5C255116EF0065770D /* ForemWebView+WKNavigationDelegate.swift in Sources */,
DC6576472559CCED00B5B46F /* ForemMediaManager+VideoActions.swift in Sources */,
DC3DAE4725229DD7004DAC13 /* ForemMediaManager.swift in Sources */,
Expand All @@ -409,6 +431,7 @@
buildActionMask = 2147483647;
files = (
DC3DAE5425229E08004DAC13 /* ViewController.swift in Sources */,
DCA40E552582C73100D463DC /* UIImage+Utilities.swift in Sources */,
DC3DAE5025229E08004DAC13 /* AppDelegate.swift in Sources */,
DC3DAE5225229E08004DAC13 /* SceneDelegate.swift in Sources */,
);
Expand All @@ -419,6 +442,7 @@
buildActionMask = 2147483647;
files = (
DC3DAE6725229E0B004DAC13 /* ExampleTests.swift in Sources */,
DCA40E562582C73100D463DC /* UIImage+Utilities.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -756,6 +780,51 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
DC8AC9FD25850EF00012DFAC /* XCRemoteSwiftPackageReference "AlamofireImage" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Alamofire/AlamofireImage.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.1.0;
};
};
DC9C22182581824B00B0F801 /* XCRemoteSwiftPackageReference "YPImagePicker" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Yummypets/YPImagePicker";
requirement = {
branch = spm;
kind = branch;
};
};
DCA40E752583C45D00D463DC /* XCRemoteSwiftPackageReference "Alamofire" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Alamofire/Alamofire.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.4.0;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
DC8AC9FE25850EF00012DFAC /* AlamofireImage */ = {
isa = XCSwiftPackageProductDependency;
package = DC8AC9FD25850EF00012DFAC /* XCRemoteSwiftPackageReference "AlamofireImage" */;
productName = AlamofireImage;
};
DC9C22192581824B00B0F801 /* YPImagePicker */ = {
isa = XCSwiftPackageProductDependency;
package = DC9C22182581824B00B0F801 /* XCRemoteSwiftPackageReference "YPImagePicker" */;
productName = YPImagePicker;
};
DCA40E762583C45E00D463DC /* Alamofire */ = {
isa = XCSwiftPackageProductDependency;
package = DCA40E752583C45D00D463DC /* XCRemoteSwiftPackageReference "Alamofire" */;
productName = Alamofire;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = DC3DAE2B25229D8C004DAC13 /* Project object */;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"object": {
"pins": [
{
"package": "Alamofire",
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
"state": {
"branch": null,
"revision": "9e0328127dfb801cefe8ac53a13c0c90a7770448",
"version": "5.4.0"
}
},
{
"package": "AlamofireImage",
"repositoryURL": "https://github.com/Alamofire/AlamofireImage.git",
"state": {
"branch": null,
"revision": "3e8edbeb75227f8542aa87f90240cf0424d6362f",
"version": "4.1.0"
}
},
{
"package": "PryntTrimmerView",
"repositoryURL": "https://github.com/HHK1/PryntTrimmerView",
"state": {
"branch": null,
"revision": "ac1b60a22c7e6a6514de7a66d2f3d5b537c956d5",
"version": "4.0.2"
}
},
{
"package": "Stevia",
"repositoryURL": "https://github.com/freshOS/Stevia",
"state": {
"branch": null,
"revision": "e1e8dc40deab3a863a35a7a0aab10f7304f4b42a",
"version": "5.1.0"
}
},
{
"package": "YPImagePicker",
"repositoryURL": "https://github.com/Yummypets/YPImagePicker",
"state": {
"branch": "spm",
"revision": "2cf2d150bb0861f2079bc44b56c17aabf5e5d5aa",
"version": null
}
}
]
},
"version": 1
}
14 changes: 10 additions & 4 deletions ForemWebView/ForemWebView+WKNavigationDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,23 @@ extension ForemWebView: WKNavigationDelegate {

// MARK: - Action Policy
func navigationPolicy(url: URL, navigationType: WKNavigationType) -> WKNavigationActionPolicy {
if foremInstance == nil {
guard let foremInstance = foremInstance else {
// First load there will be no Instance Metadata available
return .allow
} else if url.scheme == "mailto" {
foremWebViewDelegate?.requestedExternalSite(url: url)
}

if url.scheme == "mailto" {
foremWebViewDelegate?.requestedMailto(url: url)
return .cancel
} else if url.absoluteString == "about:blank" {
return .allow
} else if isOAuthUrl(url) {
return .allow
} else if url.host != foremInstance?.domain && navigationType.rawValue == 0 {
}

// localhost gives simulator support with a local server running
let isExternalDomain = (url.host != foremInstance.domain) && (url.host != "localhost")
if isExternalDomain && navigationType == .linkActivated {
foremWebViewDelegate?.requestedExternalSite(url: url)
return .cancel
} else {
Expand Down
91 changes: 91 additions & 0 deletions ForemWebView/ForemWebView+WKScriptMessageHandler.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import UIKit
import WebKit
import AlamofireImage
import YPImagePicker

enum BridgeMessageType {
case podcast, video
Expand All @@ -14,6 +17,8 @@ extension ForemWebView: WKScriptMessageHandler {
mediaManager.handleVideoMessage(message.body as? [String: String] ?? [:])
case "body":
updateUserData()
case "imageUpload":
handleImagePicker(message.body as? [String: String] ?? [:])
case "haptic":
guard let hapticType = message.body as? String else { return }
handleHapticMessage(type: hapticType)
Expand Down Expand Up @@ -62,4 +67,90 @@ extension ForemWebView: WKScriptMessageHandler {
notification.notificationOccurred(.success)
}
}

// MARK: - Image Uploads

// Builds, configures and returns an YPImagePicker
func imagePicker() -> YPImagePicker {
var config = YPImagePickerConfiguration()
config.shouldSaveNewPicturesToAlbum = false
config.startOnScreen = YPPickerScreen.library
config.library.onlySquare = false
config.library.isSquareByDefault = false
config.library.mediaType = YPlibraryMediaType.photo
return YPImagePicker(configuration: config)
}

// Whenever a request to select an image is triggered via WKScriptMessageHandler
func handleImagePicker(_ message: [String: String]) {
guard let targetElementId = message["id"] else { return }

let picker = imagePicker()
picker.didFinishPicking { [unowned picker] items, _ in
// Callback for when the native image picker process is completed by the user
if let photo = items.singlePhoto {
// Image selected now start uploading process
let message = ["action": "uploading"]
self.injectImageMessage(message, targetElementId: targetElementId)
self.uploadImage(elementId: targetElementId, image: photo.image)
}
picker.dismiss(animated: true, completion: nil)
}

// Use 'foremWebViewDelegate' as the 'pivot' ViewController to present the native picker
if let delegateViewController = foremWebViewDelegate as? UIViewController {
delegateViewController.present(picker, animated: true, completion: nil)
}
}

// This function will inject a message back into the image selector that triggered the request
func injectImageMessage(_ message: [String: String], targetElementId: String) {
var jsonString = ""
let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(message) {
jsonString = String(data: jsonData, encoding: .utf8) ?? ""
}

// React doesn't trigger `onChange` when updating the value of inputs
// programmatically, so we are forced to dispatch the event manually
let javascript = """
let element = document.getElementById('\(targetElementId)');
element.value = `\(jsonString)`;
let changeEvent = new Event('change', { bubbles: true });
element.dispatchEvent(changeEvent);
"""
evaluateJavaScript(wrappedJS(javascript)) { _, error in
guard error == nil else {
print(error.debugDescription)
return
}
}
}

// Function that will upload a UIImage directly to the Forem instance
func uploadImage(elementId: String, image: UIImage) {
guard let token = csrfToken, let domain = self.foremInstance?.domain else {
let message = ["action": "error", "message": "Unexpected error"]
self.injectImageMessage(message, targetElementId: elementId)
return
}

// Support the simulator
let requestProtocol = domain == "localhost:3000" ? "http://" : "https://"
let targetUrl = "\(requestProtocol)\(domain)/image_uploads"

let optimizedImage = image.af.imageScaled(to: image.foremLimitedSize())
optimizedImage.uploadTo(url: targetUrl, token: token) { (link, error) in
if let link = link as String? {
var message = ["action": "success", "link": link]
if !link.contains(requestProtocol) {
message["link"] = "\(requestProtocol)\(domain)\(link)"
}
self.injectImageMessage(message, targetElementId: elementId)
} else {
let message = ["action": "error", "error": error ?? "Unexpected error"]
self.injectImageMessage(message, targetElementId: elementId)
}
}
}
}
Loading

0 comments on commit dc1e7f2

Please sign in to comment.