diff --git a/Extensions/UIViewController+HideHomeIndicator.swift b/Extensions/UIViewController+HideHomeIndicator.swift index 0e6463f5..b1bcebb7 100644 --- a/Extensions/UIViewController+HideHomeIndicator.swift +++ b/Extensions/UIViewController+HideHomeIndicator.swift @@ -6,8 +6,10 @@ extension UIViewController { } public class func swizzleHomeIndicatorProperty() { - swizzle(origSelector: #selector(getter: UIViewController.prefersHomeIndicatorAutoHidden), - withSelector: #selector(getter: UIViewController.swizzle_prefersHomeIndicatorAutoHidden), - forClass: UIViewController.self) + swizzle( + origSelector: #selector(getter: UIViewController.prefersHomeIndicatorAutoHidden), + withSelector: #selector(getter: UIViewController.swizzle_prefersHomeIndicatorAutoHidden), + forClass: UIViewController.self + ) } } diff --git a/Model/Applications/InvidiousAPI.swift b/Model/Applications/InvidiousAPI.swift index d0a94b41..c3c1084e 100644 --- a/Model/Applications/InvidiousAPI.swift +++ b/Model/Applications/InvidiousAPI.swift @@ -81,7 +81,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI { configureTransformer(pathPattern("search/suggestions"), requestMethods: [.get]) { (content: Entity) -> [String] in if let suggestions = content.json.dictionaryValue["suggestions"] { - return suggestions.arrayValue.map { $0.stringValue.replacingHTMLEntities } + return suggestions.arrayValue.map(\.stringValue).map(\.replacingHTMLEntities) } return [] diff --git a/Model/Cache/FeedCacheModel.swift b/Model/Cache/FeedCacheModel.swift index bd71d10a..bdea73fb 100644 --- a/Model/Cache/FeedCacheModel.swift +++ b/Model/Cache/FeedCacheModel.swift @@ -22,7 +22,7 @@ struct FeedCacheModel: CacheModel { let date = iso8601DateFormatter.string(from: Date()) logger.info("caching feed \(account.feedCacheKey) -- \(date)") let feedTimeObject: JSON = ["date": date] - let videosObject: JSON = ["videos": videos.prefix(cacheLimit).map { $0.json.object }] + let videosObject: JSON = ["videos": videos.prefix(cacheLimit).map(\.json.object)] try? storage?.setObject(feedTimeObject, forKey: feedTimeCacheKey(account.feedCacheKey)) try? storage?.setObject(videosObject, forKey: account.feedCacheKey) } diff --git a/Model/Cache/PlaylistsCacheModel.swift b/Model/Cache/PlaylistsCacheModel.swift index ca791cc7..6201f4c0 100644 --- a/Model/Cache/PlaylistsCacheModel.swift +++ b/Model/Cache/PlaylistsCacheModel.swift @@ -21,7 +21,7 @@ struct PlaylistsCacheModel: CacheModel { let date = iso8601DateFormatter.string(from: Date()) logger.info("caching \(playlistCacheKey(account)) -- \(date)") let feedTimeObject: JSON = ["date": date] - let playlistsObject: JSON = ["playlists": playlists.map { $0.json.object }] + let playlistsObject: JSON = ["playlists": playlists.map(\.json.object)] try? storage?.setObject(feedTimeObject, forKey: playlistTimeCacheKey(account)) try? storage?.setObject(playlistsObject, forKey: playlistCacheKey(account)) } diff --git a/Model/Channel.swift b/Model/Channel.swift index 3f2b4f61..04924053 100644 --- a/Model/Channel.swift +++ b/Model/Channel.swift @@ -152,7 +152,7 @@ struct Channel: Identifiable, Hashable { "subscriptionsText": subscriptionsText as Any, "totalViews": totalViews as Any, "verified": verified as Any, - "videos": videos.map { $0.json.object } + "videos": videos.map(\.json.object) ] } diff --git a/Model/ChannelPlaylist.swift b/Model/ChannelPlaylist.swift index bc43fde0..2628d5d8 100644 --- a/Model/ChannelPlaylist.swift +++ b/Model/ChannelPlaylist.swift @@ -19,7 +19,7 @@ struct ChannelPlaylist: Identifiable { "title": title, "thumbnailURL": thumbnailURL?.absoluteString ?? "", "channel": channel?.json.object ?? "", - "videos": videos.map { $0.json.object }, + "videos": videos.map(\.json.object), "videosCount": String(videosCount ?? 0) ] } diff --git a/Model/CommentsModel.swift b/Model/CommentsModel.swift index 7ca43851..40e87c8d 100644 --- a/Model/CommentsModel.swift +++ b/Model/CommentsModel.swift @@ -42,7 +42,7 @@ final class CommentsModel: ObservableObject { .comments(video.videoID, page: page)? .load() .onSuccess { [weak self] response in - guard let self = self else { return } + guard let self else { return } if let commentsPage: CommentsPage = response.typedContent() { self.all += commentsPage.comments self.nextPage = commentsPage.nextPage diff --git a/Model/Country.swift b/Model/Country.swift index 11908a14..597030e0 100644 --- a/Model/Country.swift +++ b/Model/Country.swift @@ -274,7 +274,7 @@ extension Country { private static func filteredCountries(_ predicate: (String) -> Bool) -> [Country] { Country.allCases - .map { $0.name } + .map(\.name) .filter(predicate) .compactMap { string in Country.allCases.first { $0.name == string } } } diff --git a/Model/FeedModel.swift b/Model/FeedModel.swift index a3ceb0ad..8fc85143 100644 --- a/Model/FeedModel.swift +++ b/Model/FeedModel.swift @@ -121,7 +121,7 @@ final class FeedModel: ObservableObject, CacheModel { backgroundContext.perform { [weak self] in guard let self else { return } - let watched = self.watchFetchRequestResult(feed, context: self.backgroundContext).filter { $0.finished } + let watched = self.watchFetchRequestResult(feed, context: self.backgroundContext).filter(\.finished) let unwatched = feed.filter { video in !watched.contains { $0.videoID == video.videoID } } let unwatchedCount = max(0, feed.count - watched.count) diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index 941694bf..c4be82f7 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -90,7 +90,7 @@ final class PlayerModel: ObservableObject { }} @Published var aspectRatio = VideoPlayerView.defaultAspectRatio @Published var stream: Stream? - @Published var currentRate: Double = 1.0 { didSet { handleCurrentRateChange() } } + @Published var currentRate = 1.0 { didSet { handleCurrentRateChange() } } @Published var qualityProfileSelection: QualityProfile? { didSet { handleQualityProfileChange() } } diff --git a/Model/Player/PlayerQueue.swift b/Model/Player/PlayerQueue.swift index 8b202d11..90e2c582 100644 --- a/Model/Player/PlayerQueue.swift +++ b/Model/Player/PlayerQueue.swift @@ -74,7 +74,7 @@ extension PlayerModel { preservedTime = currentItem.playbackTime DispatchQueue.main.async { [weak self] in - guard let self = self else { return } + guard let self else { return } guard let video = item.video else { return } diff --git a/Model/Player/ScreenSaverManager.swift b/Model/Player/ScreenSaverManager.swift index 8eeed900..190de5d3 100644 --- a/Model/Player/ScreenSaverManager.swift +++ b/Model/Player/ScreenSaverManager.swift @@ -16,10 +16,12 @@ struct ScreenSaverManager { return false } - noSleepReturn = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep as CFString, - IOPMAssertionLevel(kIOPMAssertionLevelOn), - reason as CFString, - &noSleepAssertion) + noSleepReturn = IOPMAssertionCreateWithName( + kIOPMAssertionTypeNoDisplaySleep as CFString, + IOPMAssertionLevel(kIOPMAssertionLevelOn), + reason as CFString, + &noSleepAssertion + ) return noSleepReturn == kIOReturnSuccess } diff --git a/Model/QualityProfile.swift b/Model/QualityProfile.swift index 6906d581..2b216763 100644 --- a/Model/QualityProfile.swift +++ b/Model/QualityProfile.swift @@ -100,7 +100,7 @@ struct QualityProfileBridge: Defaults.Bridge { "name": value.name ?? "", "backend": value.backend.rawValue, "resolution": value.resolution.rawValue, - "formats": value.formats.map { $0.rawValue }.joined(separator: Self.formatsSeparator), + "formats": value.formats.map(\.rawValue).joined(separator: Self.formatsSeparator), "order": value.order.map { String($0) }.joined(separator: Self.formatsSeparator) // New line ] } diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index f7b7415f..f01a0815 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -607,7 +607,7 @@ enum SponsorBlockColors: String { case music_offtopic = "#FF9900" // Orange // Define all cases, can be used to iterate over the colors - static let allCases: [SponsorBlockColors] = [.sponsor, .selfpromo, .interaction, .intro, .outro, .preview, .filler, .music_offtopic] + static let allCases: [SponsorBlockColors] = [Self.sponsor, Self.selfpromo, Self.interaction, Self.intro, Self.outro, Self.preview, Self.filler, Self.music_offtopic] // Create a dictionary with the category names as keys and colors as values static let dictionary: [String: String] = { diff --git a/Shared/LanguageCodes.swift b/Shared/LanguageCodes.swift index 74fd00e5..038cb21e 100644 --- a/Shared/LanguageCodes.swift +++ b/Shared/LanguageCodes.swift @@ -51,59 +51,108 @@ enum LanguageCodes: String, CaseIterable { var description: String { switch self { - case .Afrikaans: return "Afrikaans" - case .Arabic: return "Arabic" - case .Azerbaijani: return "Azerbaijani" - case .Bengali: return "Bengali" - case .Catalan: return "Catalan" - case .Czech: return "Czech" - case .Welsh: return "Welsh" - case .Danish: return "Danish" - case .German: return "German" - case .Greek: return "Greek" - case .English: return "English" - case .English_GB: return "English (United Kingdom)" - case .Spanish: return "Spanish" - case .Persian: return "Persian" - case .Finnish: return "Finnish" - case .Filipino: return "Filipino" - case .French: return "French" - case .Irish: return "Irish" - case .Hebrew: return "Hebrew" - case .Hindi: return "Hindi" - case .Hungarian: return "Hungarian" - case .Indonesian: return "Indonesian" - case .Italian: return "Italian" - case .Japanese: return "Japanese" - case .Javanese: return "Javanese" - case .Korean: return "Korean" - case .Lithuanian: return "Lithuanian" - case .Malay: return "Malay" - case .Maltese: return "Maltese" - case .Dutch: return "Dutch" - case .Norwegian: return "Norwegian" - case .Polish: return "Polish" - case .Portuguese: return "Portuguese" - case .Romanian: return "Romanian" - case .Russian: return "Russian" - case .Slovak: return "Slovak" - case .Slovene: return "Slovene" - case .Swedish: return "Swedish" - case .Swahili: return "Swahili" - case .Thai: return "Thai" - case .Tagalog: return "Tagalog" - case .Turkish: return "Turkish" - case .Ukrainian: return "Ukrainian" - case .Urdu: return "Urdu" - case .Uzbek: return "Uzbek" - case .Vietnamese: return "Vietnamese" - case .Xhosa: return "Xhosa" - case .Chinese: return "Chinese" - case .Zulu: return "Zulu" + case .Afrikaans: + return "Afrikaans" + case .Arabic: + return "Arabic" + case .Azerbaijani: + return "Azerbaijani" + case .Bengali: + return "Bengali" + case .Catalan: + return "Catalan" + case .Czech: + return "Czech" + case .Welsh: + return "Welsh" + case .Danish: + return "Danish" + case .German: + return "German" + case .Greek: + return "Greek" + case .English: + return "English" + case .English_GB: + return "English (United Kingdom)" + case .Spanish: + return "Spanish" + case .Persian: + return "Persian" + case .Finnish: + return "Finnish" + case .Filipino: + return "Filipino" + case .French: + return "French" + case .Irish: + return "Irish" + case .Hebrew: + return "Hebrew" + case .Hindi: + return "Hindi" + case .Hungarian: + return "Hungarian" + case .Indonesian: + return "Indonesian" + case .Italian: + return "Italian" + case .Japanese: + return "Japanese" + case .Javanese: + return "Javanese" + case .Korean: + return "Korean" + case .Lithuanian: + return "Lithuanian" + case .Malay: + return "Malay" + case .Maltese: + return "Maltese" + case .Dutch: + return "Dutch" + case .Norwegian: + return "Norwegian" + case .Polish: + return "Polish" + case .Portuguese: + return "Portuguese" + case .Romanian: + return "Romanian" + case .Russian: + return "Russian" + case .Slovak: + return "Slovak" + case .Slovene: + return "Slovene" + case .Swedish: + return "Swedish" + case .Swahili: + return "Swahili" + case .Thai: + return "Thai" + case .Tagalog: + return "Tagalog" + case .Turkish: + return "Turkish" + case .Ukrainian: + return "Ukrainian" + case .Urdu: + return "Urdu" + case .Uzbek: + return "Uzbek" + case .Vietnamese: + return "Vietnamese" + case .Xhosa: + return "Xhosa" + case .Chinese: + return "Chinese" + case .Zulu: + return "Zulu" } } static func languageName(for code: String) -> String { - return LanguageCodes(rawValue: code)?.description ?? "Unknown" + return Self(rawValue: code)?.description ?? "Unknown" } } diff --git a/Shared/OpenURLHandler.swift b/Shared/OpenURLHandler.swift index 32ba19c1..12c69eff 100644 --- a/Shared/OpenURLHandler.swift +++ b/Shared/OpenURLHandler.swift @@ -209,7 +209,7 @@ struct OpenURLHandler { return accounts.api.channelByName(name) } - if let instance = InstancesModel.shared.all.first(where: { $0.app.supportsOpeningChannelsByName }) { + if let instance = InstancesModel.shared.all.first(where: \.app.supportsOpeningChannelsByName) { return instance.anonymous.channelByName(name) } @@ -223,7 +223,7 @@ struct OpenURLHandler { return accounts.api.channelByUsername(username) } - if let instance = InstancesModel.shared.all.first(where: { $0.app.supportsOpeningChannelsByName }) { + if let instance = InstancesModel.shared.all.first(where: \.app.supportsOpeningChannelsByName) { return instance.anonymous.channelByUsername(username) } diff --git a/Shared/Player/AppleAVPlayerViewController.swift b/Shared/Player/AppleAVPlayerViewController.swift index c94ebed1..c3b59bc4 100644 --- a/Shared/Player/AppleAVPlayerViewController.swift +++ b/Shared/Player/AppleAVPlayerViewController.swift @@ -61,7 +61,8 @@ final class AppleAVPlayerViewController: UIViewController { _ sections: [NowPlayingView.ViewSection], title: String ) -> UIHostingController { - let controller = UIHostingController(rootView: + let controller = UIHostingController( + rootView: AnyView( NowPlayingView(sections: sections, inInfoViewController: true) .frame(maxHeight: 600) diff --git a/Shared/Player/Controls/TimelineView.swift b/Shared/Player/Controls/TimelineView.swift index bcb126fd..5a0b291a 100644 --- a/Shared/Player/Controls/TimelineView.swift +++ b/Shared/Player/Controls/TimelineView.swift @@ -33,7 +33,7 @@ struct TimelineView: View { @State private var dragOffset: Double = 0 @State private var draggedFrom: Double = 0 - private var start: Double = 0.0 + private var start = 0.0 private var height = 8.0 var cornerRadius: Double diff --git a/Shared/Player/PlayerViewController.swift b/Shared/Player/PlayerViewController.swift index c023dd49..911a6450 100644 --- a/Shared/Player/PlayerViewController.swift +++ b/Shared/Player/PlayerViewController.swift @@ -90,8 +90,9 @@ final class PlayerViewController: UIViewController { _ sections: [NowPlayingView.ViewSection], title: String ) -> UIHostingController { - let controller = UIHostingController(rootView: - AnyView( + let controller = UIHostingController( + rootView: + AnyV‚iew( NowPlayingView(sections: sections, inInfoViewController: true) .frame(maxHeight: 600) .environmentObject(commentsModel) diff --git a/Shared/Player/Video Details/VideoDetails.swift b/Shared/Player/Video Details/VideoDetails.swift index 2656ee2d..061c5246 100644 --- a/Shared/Player/Video Details/VideoDetails.swift +++ b/Shared/Player/Video Details/VideoDetails.swift @@ -208,6 +208,7 @@ struct VideoDetails: View { .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) .padding(.horizontal, 16) + // swiftlint:disable trailing_closure #if !os(tvOS) .tapRecognizer( tapSensitivity: 0.2, @@ -218,6 +219,7 @@ struct VideoDetails: View { } ) #endif + // swiftlint:enable trailing_closure VideoActions(video: player.videoForDisplay) .padding(.vertical, 5) diff --git a/Shared/Player/VideoPlayerView.swift b/Shared/Player/VideoPlayerView.swift index a08294f2..16a52d8c 100644 --- a/Shared/Player/VideoPlayerView.swift +++ b/Shared/Player/VideoPlayerView.swift @@ -281,7 +281,7 @@ struct VideoPlayerView: View { } .gesture(player.controls.presentingOverlays ? nil : playerDragGesture) #if os(macOS) - .onAppear(perform: { + .onAppear { NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) { hoverThrottle.execute { if !player.currentItem.isNil, hoveringPlayer { @@ -291,7 +291,7 @@ struct VideoPlayerView: View { return $0 } - }) + } #endif .background(Color.black) diff --git a/Shared/Search/SearchTextField.swift b/Shared/Search/SearchTextField.swift index 78a316b0..b5077870 100644 --- a/Shared/Search/SearchTextField.swift +++ b/Shared/Search/SearchTextField.swift @@ -15,7 +15,7 @@ struct SearchTextField: View { #if os(macOS) Image(systemName: "magnifyingglass") .resizable() - .aspectRatio(contentMode: .fill) + .scaledToFill() .frame(width: 12, height: 12) .padding(.horizontal, 8) .opacity(0.8) diff --git a/Shared/Settings/QualityProfileForm.swift b/Shared/Settings/QualityProfileForm.swift index 10a99fe9..d07a7e43 100644 --- a/Shared/Settings/QualityProfileForm.swift +++ b/Shared/Settings/QualityProfileForm.swift @@ -380,7 +380,7 @@ struct QualityProfileForm: View { func submitForm() { guard valid else { return } - let activeFormats = orderedFormats.filter { $0.isActive }.map { $0.format } + let activeFormats = orderedFormats.filter(\.isActive).map(\.format) let formProfile = QualityProfile( id: qualityProfile?.id ?? UUID().uuidString, diff --git a/Shared/Subscriptions/FeedView.swift b/Shared/Subscriptions/FeedView.swift index 7e63c991..12a4a8b0 100644 --- a/Shared/Subscriptions/FeedView.swift +++ b/Shared/Subscriptions/FeedView.swift @@ -15,7 +15,7 @@ struct FeedView: View { #endif var videos: [ContentItem] { - guard let selectedChannel = selectedChannel else { + guard let selectedChannel else { return ContentItem.array(of: feed.videos) } return ContentItem.array(of: feed.videos.filter { @@ -24,9 +24,7 @@ struct FeedView: View { } var channels: [Channel] { - feed.videos.map { - $0.channel - }.unique() + feed.videos.map(\.channel).unique() } @State private var selectedChannel: Channel? @@ -272,7 +270,7 @@ struct FeedView: View { } var channelHeaderView: some View { - guard let selectedChannel = selectedChannel else { + guard let selectedChannel else { return AnyView( Text("All Channels") .font(.caption) diff --git a/Shared/Trending/TrendingCountry.swift b/Shared/Trending/TrendingCountry.swift index 04e22873..2f53db1c 100644 --- a/Shared/Trending/TrendingCountry.swift +++ b/Shared/Trending/TrendingCountry.swift @@ -6,7 +6,7 @@ struct TrendingCountry: View { @StateObject private var store = Store(Country.allCases) - @State private var query: String = "" + @State private var query = "" @State private var selection: Country? @Environment(\.colorScheme) private var colorScheme diff --git a/Shared/URLParser.swift b/Shared/URLParser.swift index 1741d700..f7f4109c 100644 --- a/Shared/URLParser.swift +++ b/Shared/URLParser.swift @@ -26,8 +26,7 @@ struct URLParser { urlString.contains("youtube.com") || urlString.contains("youtu.be") || urlString.contains("youtube-nocookie.com"), - let url = URL(string: "https://\(urlString)" - ) + let url = URL(string: "https://\(urlString)") { self.url = url } @@ -176,10 +175,8 @@ struct URLParser { private func removePrefixes(_ value: String, _ prefixes: [String]) -> String { var value = value - for prefix in prefixes { - if value.hasPrefix(prefix) { - value.removeFirst(prefix.count) - } + for prefix in prefixes where value.hasPrefix(prefix) { + value.removeFirst(prefix.count) } return value diff --git a/Shared/YatteeApp.swift b/Shared/YatteeApp.swift index 3c07a1d8..699d6204 100644 --- a/Shared/YatteeApp.swift +++ b/Shared/YatteeApp.swift @@ -100,7 +100,7 @@ struct YatteeApp: App { .commands { SidebarCommands() - CommandGroup(replacing: .newItem, addition: {}) + CommandGroup(replacing: .newItem) {} MenuCommands(model: Binding(get: { MenuModel.shared }, set: { _ in })) } diff --git a/iOS/OrientationTracker.swift b/iOS/OrientationTracker.swift index 0f8eda58..e7bae697 100644 --- a/iOS/OrientationTracker.swift +++ b/iOS/OrientationTracker.swift @@ -65,9 +65,11 @@ public class OrientationTracker { guard newDeviceOrientation != self.currentDeviceOrientation else { return } self.currentDeviceOrientation = newDeviceOrientation - NotificationCenter.default.post(name: Self.deviceOrientationChangedNotification, - object: nil, - userInfo: nil) + NotificationCenter.default.post( + name: Self.deviceOrientationChangedNotification, + object: nil, + userInfo: nil + ) } } diff --git a/iOS/ShareSheet.swift b/iOS/ShareSheet.swift index 59cc6737..9abc8458 100644 --- a/iOS/ShareSheet.swift +++ b/iOS/ShareSheet.swift @@ -4,7 +4,7 @@ import SwiftUI struct ShareSheet: UIViewControllerRepresentable { typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, - _ returnedItems: [Any]?, + _ returnedItems: [Any], _ error: Error?) -> Void let activityItems: [Any] @@ -19,7 +19,10 @@ struct ShareSheet: UIViewControllerRepresentable { ) controller.excludedActivityTypes = excludedActivityTypes - controller.completionWithItemsHandler = callback + + controller.completionWithItemsHandler = { activityType, completed, returnedItems, error in + callback?(activityType, completed, returnedItems ?? [], error) + } return controller } diff --git a/macOS/VideoLayer.swift b/macOS/VideoLayer.swift index a3a83c55..c37b0d06 100644 --- a/macOS/VideoLayer.swift +++ b/macOS/VideoLayer.swift @@ -38,10 +38,12 @@ final class VideoLayer: CAOpenGLLayer { glGetIntegerv(GLenum(GL_DRAW_FRAMEBUFFER_BINDING), &i) if client.mpvGL != nil { - var data = mpv_opengl_fbo(fbo: Int32(i), - w: Int32(bounds.size.width), - h: Int32(bounds.size.height), - internal_format: 0) + var data = mpv_opengl_fbo( + fbo: Int32(i), + w: Int32(bounds.size.width), + h: Int32(bounds.size.height), + internal_format: 0 + ) var params: [mpv_render_param] = [ mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_FBO, data: &data), mpv_render_param(type: MPV_RENDER_PARAM_FLIP_Y, data: &flip), @@ -106,8 +108,10 @@ final class VideoLayer: CAOpenGLLayer { let displayId = UInt32(NSScreen.main?.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as! Int) CVDisplayLinkCreateWithCGDisplay(displayId, &client.link) - CVDisplayLinkSetOutputCallback(client.link!, displayLinkCallback, - UnsafeMutableRawPointer(Unmanaged.passUnretained(client.layer).toOpaque())) + CVDisplayLinkSetOutputCallback( + client.link!, displayLinkCallback, + UnsafeMutableRawPointer(Unmanaged.passUnretained(client.layer).toOpaque()) + ) CVDisplayLinkStart(client.link!) }