Skip to content

Commit

Permalink
wip: use swizzle instead of SceneDelegate and improvements for macOS
Browse files Browse the repository at this point in the history
  • Loading branch information
ErrorErrorError committed Nov 29, 2023
1 parent ceeb910 commit 29a5fd7
Show file tree
Hide file tree
Showing 59 changed files with 1,437 additions and 744 deletions.
20 changes: 10 additions & 10 deletions App/Mochi.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@

/* Begin PBXBuildFile section */
132862252A17D06300F67EAC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 132862242A17D06300F67EAC /* AppDelegate.swift */; };
1396B9E42A4B71BA00B7928A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1396B9E32A4B71BA00B7928A /* SceneDelegate.swift */; };
1396B9E62A4B72A800B7928A /* HostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1396B9E52A4B72A800B7928A /* HostingController.swift */; };
1396B9E62A4B72A800B7928A /* PreferenceHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1396B9E52A4B72A800B7928A /* PreferenceHostingController.swift */; };
1396FE0529DF561C00B22132 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1396FE0029DF561C00B22132 /* Assets.xcassets */; };
1396FE0629DF561C00B22132 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1396FE0229DF561C00B22132 /* Preview Assets.xcassets */; };
1396FE0729DF561C00B22132 /* MochiApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1396FE0329DF561C00B22132 /* MochiApp.swift */; };
13EDE7392B166E4500E14998 /* PreferenceHostingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13EDE7382B166E4500E14998 /* PreferenceHostingView.swift */; };
13F11CC02B11617D006FFF63 /* App in Frameworks */ = {isa = PBXBuildFile; productRef = 13F11CBF2B11617D006FFF63 /* App */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
132862242A17D06300F67EAC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
134516B629DF44D200E4C3B8 /* mochi */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = mochi; path = ..; sourceTree = "<group>"; };
138DA7D52A0AB5E800FDAC13 /* mochi-info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "mochi-info.plist"; sourceTree = "<group>"; };
1396B9E32A4B71BA00B7928A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
1396B9E52A4B72A800B7928A /* HostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostingController.swift; sourceTree = "<group>"; };
1396B9E52A4B72A800B7928A /* PreferenceHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceHostingController.swift; sourceTree = "<group>"; };
1396FDFF29DF561C00B22132 /* mochi.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = mochi.entitlements; sourceTree = "<group>"; };
1396FE0029DF561C00B22132 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
1396FE0229DF561C00B22132 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
1396FE0329DF561C00B22132 /* MochiApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MochiApp.swift; sourceTree = "<group>"; };
13C18B9129CE6CC200C14F26 /* Mochi.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mochi.app; sourceTree = BUILT_PRODUCTS_DIR; };
13EDE7382B166E4500E14998 /* PreferenceHostingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceHostingView.swift; sourceTree = "<group>"; };
13F11CC12B116431006FFF63 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk/System/Library/Frameworks/Accelerate.framework; sourceTree = DEVELOPER_DIR; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -81,8 +81,8 @@
1396FE0429DF561C00B22132 /* iOS */ = {
isa = PBXGroup;
children = (
1396B9E32A4B71BA00B7928A /* SceneDelegate.swift */,
1396B9E52A4B72A800B7928A /* HostingController.swift */,
1396B9E52A4B72A800B7928A /* PreferenceHostingController.swift */,
13EDE7382B166E4500E14998 /* PreferenceHostingView.swift */,
);
path = iOS;
sourceTree = "<group>";
Expand Down Expand Up @@ -233,8 +233,8 @@
files = (
132862252A17D06300F67EAC /* AppDelegate.swift in Sources */,
1396FE0729DF561C00B22132 /* MochiApp.swift in Sources */,
1396B9E62A4B72A800B7928A /* HostingController.swift in Sources */,
1396B9E42A4B71BA00B7928A /* SceneDelegate.swift in Sources */,
1396B9E62A4B72A800B7928A /* PreferenceHostingController.swift in Sources */,
13EDE7392B166E4500E14998 /* PreferenceHostingView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -385,7 +385,7 @@
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = 0.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.errorerrorerror.mochi;
PRODUCT_BUNDLE_IDENTIFIER = dev.errorerrorerror.mochi;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
Expand Down Expand Up @@ -424,7 +424,7 @@
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = 0.0.1;
PRODUCT_BUNDLE_IDENTIFIER = com.errorerrorerror.mochi;
PRODUCT_BUNDLE_IDENTIFIER = dev.errorerrorerror.mochi;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
Expand Down
31 changes: 7 additions & 24 deletions App/Shared/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,24 @@ import App
import Architecture
import Foundation

#if os(iOS)
#if canImport(UIKit)
import UIKit

let store = Store(
initialState: .init(),
reducer: { AppFeature() }
)

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
let store = Store(
initialState: .init(),
reducer: { AppFeature() }
)

func application(
_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
store.send(.internal(.appDelegate(.didFinishLaunching)))
return true
}

func application(
_: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options _: UIScene.ConnectionOptions
) -> UISceneConfiguration {
let configuration = UISceneConfiguration(
name: connectingSceneSession.configuration.name,
sessionRole: connectingSceneSession.role
)

configuration.delegateClass = SceneDelegate.self

return configuration
}
}

Check warning on line 29 in App/Shared/AppDelegate.swift

View workflow job for this annotation

GitHub Actions / run-swiftformat

Insert blank line before class, struct, enum, extension, protocol or function declarations. (blankLinesBetweenScopes)

#else
#elseif canImport(AppKit)
import AppKit

class AppDelegate: NSObject, NSApplicationDelegate {
Expand Down
26 changes: 23 additions & 3 deletions App/Shared/MochiApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,52 @@ import Settings
import SwiftUI
import VideoPlayer

#if os(macOS)
@main
struct MochiApp: App {
#if canImport(UIKit)
@UIApplicationDelegateAdaptor(AppDelegate.self)
#elseif canImport(AppKit)
@NSApplicationDelegateAdaptor(AppDelegate.self)
#endif
var appDelegate

var body: some Scene {
WindowGroup {
#if os(iOS)
PreferenceHostingView {
AppFeature.View(
store: appDelegate.store
)
}
// Ignoring safe area is required for
// PreferenceHostingView to render outside
// bounds
.ignoresSafeArea()
.themeable()
#elseif os(macOS)
AppFeature.View(
store: appDelegate.store
)
.themeable()
.frame(
minWidth: 800,
maxWidth: .infinity,
minHeight: 625,
maxHeight: .infinity
)
.themeable()
#endif
}
#if os(macOS)
.windowStyle(.titleBar)
.commands {
SidebarCommands()
ToolbarCommands()

CommandGroup(replacing: .newItem) {}
}
#endif

#if os(macOS)
Settings {
SettingsFeature.View(
store: appDelegate.store.scope(
Expand All @@ -47,7 +66,8 @@ struct MochiApp: App {
)
)
.themeable()
.frame(width: 412)
}
#endif
}
}
#endif
2 changes: 1 addition & 1 deletion App/Shared/mochi-info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>com.errorerrorerror.mochi</string>
<string>dev.errorerrorerror.mochi</string>
<key>CFBundleURLSchemes</key>
<array>
<string>mochi</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
//
//

#if os(iOS)
#if canImport(UIKit)
import Foundation
import SwiftUI
import UIKit
import ViewComponents

final class HostingController<Variant: View>: UIHostingController<Variant>, OpaqueController {
final class PreferenceHostingController<Root: View>: UIHostingController<BoxedView<Root>>, OpaquePreferenceHostingController {
override var prefersHomeIndicatorAutoHidden: Bool { _homeIndicatorAutoHidden }

var _homeIndicatorAutoHidden = false {
Expand All @@ -23,7 +23,7 @@ final class HostingController<Variant: View>: UIHostingController<Variant>, Opaq

private let box: Box

init<InnerView: View>(rootView: InnerView) where Variant == BoxedView<InnerView> {
init(rootView: @escaping () -> Root) {
self.box = .init()
super.init(rootView: .init(box: box, content: rootView))
box.object = self
Expand All @@ -35,15 +35,15 @@ final class HostingController<Variant: View>: UIHostingController<Variant>, Opaq
}
}

struct BoxedView<Variant: View>: View {
struct BoxedView<Content: View>: View {
let box: Box

init(box: Box, content: @autoclosure @escaping () -> Variant) {
init(box: Box, content: @escaping () -> Content) {
self.content = content
self.box = box
}

let content: () -> Variant
let content: () -> Content

var body: some View {
content()
Expand All @@ -54,11 +54,11 @@ struct BoxedView<Variant: View>: View {
}

final class Box {
weak var object: OpaqueController?
weak var object: OpaquePreferenceHostingController?
}

@MainActor
protocol OpaqueController: AnyObject {
protocol OpaquePreferenceHostingController: UIViewController {
var _homeIndicatorAutoHidden: Bool { get set }
}
#endif
100 changes: 100 additions & 0 deletions App/iOS/PreferenceHostingView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//
// PreferenceHostingView.swift
// Mochi
//
// Created by ErrorErrorError on 11/28/23.
//
// Source: https://gist.github.com/Amzd/01e1f69ecbc4c82c8586dcd292b1d30d

import Foundation
import SwiftUI

#if canImport(UIKit)
@MainActor
struct PreferenceHostingView<Content: View>: UIViewControllerRepresentable {
init(content: @escaping () -> Content) {
_ = UIViewController.swizzle()
self.content = content
}

let content: () -> Content

func makeUIViewController(context: Context) -> PreferenceHostingController<Content> {
PreferenceHostingController(rootView: content)
}

func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}

extension UIViewController {
static func swizzle() {
Swizzle(UIViewController.self) {
#selector(getter: childForHomeIndicatorAutoHidden) => #selector(__swizzledChildForHomeIndicatorAutoHidden)
}
}

@objc func __swizzledChildForHomeIndicatorAutoHidden() -> UIViewController? {
if self is OpaquePreferenceHostingController {
return nil
} else {
return search()
}
}

private func search() -> OpaquePreferenceHostingController? {
if let result = children.compactMap({ $0 as? OpaquePreferenceHostingController }).first {
return result
}

for child in children {
if let result = child.search() {
return result
}
}

return nil
}
}
#endif

// Move to utils?
struct Swizzle {
@discardableResult
init(
_ type: AnyClass,
@SwizzleSelectorsBuilder builder: () -> [SwizzleReplacer]
) {
builder().forEach { $0(type) }
}
}

struct SwizzleReplacer {
let original: Selector
let swizzled: Selector

func callAsFunction(_ type: AnyClass) {
guard let originalMethod = class_getInstanceMethod(type, original),
let swizzledMethod = class_getInstanceMethod(type, swizzled) else {
return
}

method_exchangeImplementations(originalMethod, swizzledMethod)
}
}

@resultBuilder
enum SwizzleSelectorsBuilder {
typealias Component = SwizzleReplacer

static func buildBlock(_ components: Component...) -> [Component] {
components
}
}

infix operator =>

extension Selector {
static func => (original: Selector, swizzled: Selector) -> SwizzleReplacer {
.init(original: original, swizzled: swizzled)
}
}
28 changes: 0 additions & 28 deletions App/iOS/SceneDelegate.swift

This file was deleted.

Loading

0 comments on commit 29a5fd7

Please sign in to comment.