Skip to content

Commit de3790d

Browse files
marcorothleonvogt
andcommitted
Initial Hotwire Native Setup
Co-Authored-By: Leon Vogt <[email protected]>
1 parent 331b5ef commit de3790d

File tree

18 files changed

+311
-201
lines changed

18 files changed

+311
-201
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# [RubyEvents.org](https://rubyevents.org) Hotwire Native iOS App

RubyEvents.xcodeproj/project.pbxproj

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
objectVersion = 77;
77
objects = {
88

9+
/* Begin PBXBuildFile section */
10+
97613B4C2D0635C600220031 /* HotwireNative in Frameworks */ = {isa = PBXBuildFile; productRef = 97613B4B2D0635C600220031 /* HotwireNative */; };
11+
97613B572D06512900220031 /* YouTubePlayerKit in Frameworks */ = {isa = PBXBuildFile; productRef = 97613B562D06512900220031 /* YouTubePlayerKit */; };
12+
/* End PBXBuildFile section */
13+
914
/* Begin PBXContainerItemProxy section */
1015
97613B2D2D0634FA00220031 /* PBXContainerItemProxy */ = {
1116
isa = PBXContainerItemProxy;
@@ -65,6 +70,8 @@
6570
isa = PBXFrameworksBuildPhase;
6671
buildActionMask = 2147483647;
6772
files = (
73+
97613B572D06512900220031 /* YouTubePlayerKit in Frameworks */,
74+
97613B4C2D0635C600220031 /* HotwireNative in Frameworks */,
6875
);
6976
runOnlyForDeploymentPostprocessing = 0;
7077
};
@@ -125,6 +132,8 @@
125132
);
126133
name = RubyEvents;
127134
packageProductDependencies = (
135+
97613B4B2D0635C600220031 /* HotwireNative */,
136+
97613B562D06512900220031 /* YouTubePlayerKit */,
128137
);
129138
productName = RubyEvents;
130139
productReference = 97613B162D0634F500220031 /* RubyEvents.app */;
@@ -208,6 +217,10 @@
208217
);
209218
mainGroup = 97613B0D2D0634F500220031;
210219
minimizedProjectReferenceProxies = 1;
220+
packageReferences = (
221+
97613B4A2D0635C600220031 /* XCRemoteSwiftPackageReference "hotwire-native-ios" */,
222+
97613B552D06512900220031 /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */,
223+
);
211224
preferredProjectObjectVersion = 77;
212225
productRefGroup = 97613B172D0634F500220031 /* Products */;
213226
projectDirPath = "";
@@ -289,6 +302,7 @@
289302
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
290303
CODE_SIGN_STYLE = Automatic;
291304
CURRENT_PROJECT_VERSION = 1;
305+
DEVELOPMENT_TEAM = NAQKG42NC3;
292306
GENERATE_INFOPLIST_FILE = YES;
293307
INFOPLIST_FILE = RubyEvents/Info.plist;
294308
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@@ -316,6 +330,7 @@
316330
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
317331
CODE_SIGN_STYLE = Automatic;
318332
CURRENT_PROJECT_VERSION = 1;
333+
DEVELOPMENT_TEAM = NAQKG42NC3;
319334
GENERATE_INFOPLIST_FILE = YES;
320335
INFOPLIST_FILE = RubyEvents/Info.plist;
321336
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@@ -563,6 +578,38 @@
563578
defaultConfigurationName = Release;
564579
};
565580
/* End XCConfigurationList section */
581+
582+
/* Begin XCRemoteSwiftPackageReference section */
583+
97613B4A2D0635C600220031 /* XCRemoteSwiftPackageReference "hotwire-native-ios" */ = {
584+
isa = XCRemoteSwiftPackageReference;
585+
repositoryURL = "https://github.com/hotwired/hotwire-native-ios";
586+
requirement = {
587+
kind = upToNextMajorVersion;
588+
minimumVersion = 1.0.1;
589+
};
590+
};
591+
97613B552D06512900220031 /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */ = {
592+
isa = XCRemoteSwiftPackageReference;
593+
repositoryURL = "https://github.com/SvenTiigi/YouTubePlayerKit.git";
594+
requirement = {
595+
kind = upToNextMajorVersion;
596+
minimumVersion = 1.9.0;
597+
};
598+
};
599+
/* End XCRemoteSwiftPackageReference section */
600+
601+
/* Begin XCSwiftPackageProductDependency section */
602+
97613B4B2D0635C600220031 /* HotwireNative */ = {
603+
isa = XCSwiftPackageProductDependency;
604+
package = 97613B4A2D0635C600220031 /* XCRemoteSwiftPackageReference "hotwire-native-ios" */;
605+
productName = HotwireNative;
606+
};
607+
97613B562D06512900220031 /* YouTubePlayerKit */ = {
608+
isa = XCSwiftPackageProductDependency;
609+
package = 97613B552D06512900220031 /* XCRemoteSwiftPackageReference "YouTubePlayerKit" */;
610+
productName = YouTubePlayerKit;
611+
};
612+
/* End XCSwiftPackageProductDependency section */
566613
};
567614
rootObject = 97613B0E2D0634F500220031 /* Project object */;
568615
}

RubyEvents.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

RubyEvents/AppDelegate.swift

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
//
2-
// AppDelegate.swift
3-
// RubyEvents
4-
//
5-
// Created by Marco Roth on 08.12.2024.
6-
//
7-
81
import UIKit
92

103
@main
114
class AppDelegate: UIResponder, UIApplicationDelegate {
125

13-
14-
156
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
167
// Override point for customization after application launch.
178
return true
@@ -30,7 +21,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
3021
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
3122
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
3223
}
33-
34-
3524
}
36-

RubyEvents/Appearence.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import UIKit
2+
3+
struct Appearance {
4+
static func configure() {
5+
configureNavigationBar()
6+
}
7+
8+
private static func configureNavigationBar() {
9+
let navigationBarAppearance = UINavigationBarAppearance()
10+
navigationBarAppearance.configureWithDefaultBackground()
11+
navigationBarAppearance.backgroundColor = .white
12+
13+
UINavigationBar.appearance().standardAppearance = navigationBarAppearance
14+
UINavigationBar.appearance().scrollEdgeAppearance = navigationBarAppearance
15+
UINavigationBar.appearance().isOpaque = true
16+
UINavigationBar.appearance().isTranslucent = false
17+
UINavigationBar.appearance().tintColor = .black
18+
19+
navigationBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.black]
20+
}
21+
}
Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
22
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
3-
<dependencies>
4-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
5-
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
6-
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
7-
</dependencies>
8-
<scenes>
9-
<!--View Controller-->
10-
<scene sceneID="EHf-IW-A2E">
11-
<objects>
12-
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
13-
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
14-
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
15-
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16-
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
17-
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
18-
</view>
19-
</viewController>
20-
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
21-
</objects>
22-
<point key="canvasLocation" x="53" y="375"/>
23-
</scene>
24-
</scenes>
3+
<dependencies>
4+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
5+
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
6+
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
7+
</dependencies>
8+
<scenes>
9+
<!--View Controller-->
10+
<scene sceneID="EHf-IW-A2E">
11+
<objects>
12+
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
13+
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
14+
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
15+
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16+
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
17+
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
18+
</view>
19+
</viewController>
20+
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
21+
</objects>
22+
<point key="canvasLocation" x="53" y="375"/>
23+
</scene>
24+
</scenes>
2525
</document>
Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
3-
<dependencies>
4-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
5-
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
6-
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
7-
</dependencies>
8-
<scenes>
9-
<!--View Controller-->
10-
<scene sceneID="tne-QT-ifu">
11-
<objects>
12-
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
13-
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
14-
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
15-
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16-
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
17-
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
18-
</view>
19-
</viewController>
20-
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
21-
</objects>
22-
</scene>
23-
</scenes>
3+
<dependencies>
4+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
5+
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
6+
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
7+
</dependencies>
8+
<scenes>
9+
<!--View Controller-->
10+
<scene sceneID="tne-QT-ifu">
11+
<objects>
12+
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
13+
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
14+
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
15+
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16+
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
17+
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
18+
</view>
19+
</viewController>
20+
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
21+
</objects>
22+
</scene>
23+
</scenes>
2424
</document>

RubyEvents/Endpoint.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Foundation
2+
3+
struct Endpoint {
4+
static let development = URL(string: "http://192.168.1.244:3000")!
5+
static let staging = URL(string: "https://staging.rubyvideo.dev")!
6+
static let production = URL(string: "https://rubyvideo.dev")!
7+
8+
static func url(environment: Environment = .development) -> URL {
9+
switch environment {
10+
case .development: return development
11+
case .staging: return staging
12+
case .production: return production
13+
}
14+
}
15+
}

RubyEvents/Environment.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
enum Environment {
2+
case development
3+
case staging
4+
case production
5+
}

RubyEvents/HotwireNavigator.swift

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import HotwireNative
2+
import Foundation
3+
import UIKit
4+
import WebKit
5+
import YouTubePlayerKit
6+
7+
class HotwireNavigator {
8+
static let instance = HotwireNavigator()
9+
10+
let sharedProcessPool = WKProcessPool()
11+
let router = Router(environment: .development)
12+
13+
var window: UIWindow?
14+
var navigator: Navigator!
15+
var pathConfiguration: PathConfiguration
16+
17+
private init(){
18+
self.pathConfiguration = PathConfiguration(sources: [
19+
.file(Bundle.main.url(forResource: "path-configuration", withExtension: "json")!),
20+
.server(router.pathConfigurationURL())
21+
])
22+
23+
configureHotwire()
24+
self.navigator = Navigator(pathConfiguration: pathConfiguration, delegate: self)
25+
}
26+
27+
func configureHotwire() {
28+
let versionNumber = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String
29+
let uniqueDeviceId = UIDevice.current.identifierForVendor?.uuidString ?? ""
30+
31+
Hotwire.config.userAgent = "Hotwire Native iOS; app_version: \(versionNumber); unique_device_id: \(uniqueDeviceId);"
32+
33+
Hotwire.registerBridgeComponents([
34+
ButtonComponent.self
35+
])
36+
37+
Hotwire.config.makeCustomWebView = { configuration in
38+
configuration.processPool = self.sharedProcessPool
39+
configuration.defaultWebpagePreferences?.preferredContentMode = .mobile
40+
configuration.allowsInlineMediaPlayback = true
41+
42+
let webView = WKWebView(frame: .zero, configuration: configuration)
43+
webView.allowsLinkPreview = false
44+
45+
if #available(iOS 16.4, *) {
46+
webView.isInspectable = true
47+
}
48+
49+
Bridge.initialize(webView)
50+
51+
return webView
52+
}
53+
}
54+
55+
func didStart(url: URL?, window: UIWindow?) {
56+
self.window = window
57+
self.window?.rootViewController = HotwireNavigator.instance.navigator.rootViewController
58+
59+
navigator.route(router.rootURL())
60+
Appearance.configure()
61+
}
62+
}
63+
64+
extension HotwireNavigator: NavigatorDelegate {
65+
func handle(proposal: VisitProposal) -> ProposalResult {
66+
switch proposal.viewController {
67+
case "player":
68+
let youTubePlayer = YouTubePlayer(
69+
source: .video(id: "psL_5RIBqnY"),
70+
configuration: .init(
71+
fullscreenMode: .system,
72+
autoPlay: true,
73+
showControls: false,
74+
useModestBranding: true,
75+
playInline: true,
76+
showRelatedVideos: false
77+
)
78+
)
79+
80+
let playerViewController = YouTubePlayerViewController(
81+
player: youTubePlayer
82+
)
83+
84+
return .acceptCustom(playerViewController)
85+
default:
86+
return .accept
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)