IOS-148 Updates to the media badging look & feel (#1019)
This commit is contained in:
parent
391bc455ea
commit
124638a0cb
|
@ -130,7 +130,7 @@
|
|||
855149CA29606D6400943D96 /* PortraitAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855149C929606D6400943D96 /* PortraitAlertController.swift */; };
|
||||
85904C02293BC0EB0011C817 /* ImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85904C01293BC0EB0011C817 /* ImageProvider.swift */; };
|
||||
85904C04293BC1940011C817 /* URLActivityItemWithMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85904C03293BC1940011C817 /* URLActivityItemWithMetadata.swift */; };
|
||||
85BC11B32932414900E191CD /* AltViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BC11B22932414900E191CD /* AltViewController.swift */; };
|
||||
85BC11B32932414900E191CD /* AltTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BC11B22932414900E191CD /* AltTextViewController.swift */; };
|
||||
87FFDA5D898A5C42ADCB35E7 /* Pods_Mastodon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */; };
|
||||
9E44C7202967AD17004B2A72 /* MastodonSDKDynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 9E44C71F2967AD17004B2A72 /* MastodonSDKDynamic */; };
|
||||
9E44C7222967AD17004B2A72 /* MastodonSDKDynamic in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 9E44C71F2967AD17004B2A72 /* MastodonSDKDynamic */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
|
@ -755,7 +755,7 @@
|
|||
855149C929606D6400943D96 /* PortraitAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitAlertController.swift; sourceTree = "<group>"; };
|
||||
85904C01293BC0EB0011C817 /* ImageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProvider.swift; sourceTree = "<group>"; };
|
||||
85904C03293BC1940011C817 /* URLActivityItemWithMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLActivityItemWithMetadata.swift; sourceTree = "<group>"; };
|
||||
85BC11B22932414900E191CD /* AltViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AltViewController.swift; sourceTree = "<group>"; };
|
||||
85BC11B22932414900E191CD /* AltTextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AltTextViewController.swift; sourceTree = "<group>"; };
|
||||
8850E70A1D5FF51432E43653 /* Pods-Mastodon-MastodonUITests.asdk - release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.asdk - release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.asdk - release.xcconfig"; sourceTree = "<group>"; };
|
||||
8E79CCBE51FBC3F7FE8CF49F /* Pods-MastodonTests.release snapshot.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MastodonTests.release snapshot.xcconfig"; path = "Target Support Files/Pods-MastodonTests/Pods-MastodonTests.release snapshot.xcconfig"; sourceTree = "<group>"; };
|
||||
8ED8C4B1F1BA2DCFF2926BB1 /* Pods-Mastodon-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-NotificationService/Pods-Mastodon-NotificationService.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
|
@ -2242,7 +2242,7 @@
|
|||
DB6180F026391CAB0018D199 /* Image */,
|
||||
DB6180E1263919780018D199 /* Paging */,
|
||||
DB6180DC263918E30018D199 /* MediaPreviewViewController.swift */,
|
||||
85BC11B22932414900E191CD /* AltViewController.swift */,
|
||||
85BC11B22932414900E191CD /* AltTextViewController.swift */,
|
||||
DB6180F926391F2E0018D199 /* MediaPreviewViewModel.swift */,
|
||||
);
|
||||
path = MediaPreview;
|
||||
|
@ -3900,7 +3900,7 @@
|
|||
DB9D6BFF25E4F5940051B173 /* ProfileViewController.swift in Sources */,
|
||||
2D4AD8A226316CD200613EFC /* SelectedAccountSection.swift in Sources */,
|
||||
DB3EA8F1281B9EF600598866 /* DiscoveryCommunityViewModel+Diffable.swift in Sources */,
|
||||
85BC11B32932414900E191CD /* AltViewController.swift in Sources */,
|
||||
85BC11B32932414900E191CD /* AltTextViewController.swift in Sources */,
|
||||
DB63F775279A997D00455B82 /* NotificationTableViewCell+ViewModel.swift in Sources */,
|
||||
DB98EB5927B109890082E365 /* ReportSupplementaryViewController.swift in Sources */,
|
||||
DB0617EB277EF3820030EE79 /* GradientBorderView.swift in Sources */,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// AltViewController.swift
|
||||
// AltTextViewController.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by Jed Fox on 2022-11-26.
|
||||
|
@ -7,7 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
class AltViewController: UIViewController {
|
||||
class AltTextViewController: UIViewController {
|
||||
let textView = {
|
||||
let textView: UITextView
|
||||
|
||||
|
@ -85,7 +85,7 @@ class AltViewController: UIViewController {
|
|||
}
|
||||
|
||||
// MARK: UIPopoverPresentationControllerDelegate
|
||||
extension AltViewController: UIPopoverPresentationControllerDelegate {
|
||||
extension AltTextViewController: UIPopoverPresentationControllerDelegate {
|
||||
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
||||
.none
|
||||
}
|
|
@ -186,7 +186,7 @@ extension MediaPreviewViewController {
|
|||
@objc private func altButtonPressed(_ sender: UIButton) {
|
||||
guard let alt = viewModel.altText else { return }
|
||||
|
||||
present(AltViewController(alt: alt, sourceView: sender), animated: true)
|
||||
present(AltTextViewController(alt: alt, sourceView: sender), animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ExpandableMediaBadge<Label: View, Content: View>: View {
|
||||
@Binding private var isExpanded: Bool
|
||||
private let parentGeometry: (size: CGSize, space: AnyHashable)
|
||||
private let label: Label
|
||||
private let content: Content
|
||||
|
||||
@Namespace private var namespace
|
||||
|
||||
init(isExpanded: Binding<Bool>, in parentGeometry: (CGSize, AnyHashable), @ViewBuilder content: () -> Content, @ViewBuilder label: () -> Label) {
|
||||
self._isExpanded = isExpanded
|
||||
self.parentGeometry = parentGeometry
|
||||
self.content = content()
|
||||
self.label = label()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
MediaBadge {
|
||||
label
|
||||
}
|
||||
.opacity(0)
|
||||
.overlay {
|
||||
GeometryReader { geom in
|
||||
Color.clear
|
||||
.preference(key: OffsetRect.self, value: geom.frame(in: .named(parentGeometry.space)))
|
||||
}
|
||||
}
|
||||
.overlayPreferenceValue(OffsetRect.self, alignment: .bottomLeading) { offsetRect in
|
||||
MediaBadge {
|
||||
HStack {
|
||||
if isExpanded {
|
||||
content
|
||||
.font(.caption)
|
||||
.matchedGeometryEffect(id: "background", in: namespace, properties: .position)
|
||||
.transition(
|
||||
.scale(scale: 0.2, anchor: .bottomLeading)
|
||||
.combined(with: .opacity)
|
||||
)
|
||||
.layoutPriority(1)
|
||||
Spacer(minLength: 0)
|
||||
} else {
|
||||
label
|
||||
.matchedGeometryEffect(id: "background", in: namespace, properties: .position)
|
||||
.transition(
|
||||
.scale(scale: 3, anchor: .trailing)
|
||||
.combined(with: .opacity)
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, isExpanded ? (8 - 2) : 0)
|
||||
}
|
||||
.frame(width: isExpanded ? parentGeometry.size.width : nil)
|
||||
.offset(x: isExpanded ? -offsetRect.minX : 0)
|
||||
.animation(.spring(response: 0.3), value: isExpanded)
|
||||
// this is not accessible, but the badge UI is not shown to accessibility tools at the moment
|
||||
.onTapGesture {
|
||||
isExpanded.toggle()
|
||||
}
|
||||
}
|
||||
// necessary to keep the expanded state from underlapping the collapsed badges
|
||||
// NOTE: if you want multiple expandable badges you will need to change this somehow. Good luck!
|
||||
.zIndex(1)
|
||||
}
|
||||
}
|
||||
|
||||
extension ExpandableMediaBadge where Label == Text {
|
||||
init(_ label: String, isExpanded: Binding<Bool>, in parentGeometry: (CGSize, AnyHashable), @ViewBuilder content: () -> Content) {
|
||||
self.init(isExpanded: isExpanded, in: parentGeometry, content: content) {
|
||||
Text(label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct OffsetRect: PreferenceKey {
|
||||
static var defaultValue = CGRect.zero
|
||||
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
|
||||
value = nextValue()
|
||||
}
|
||||
}
|
||||
|
||||
struct ExpandableMediaBadge_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
GeometryReader { geom in
|
||||
ExpandableMediaBadge(isExpanded: .constant(false), in: (geom.size, "preview")) {
|
||||
Text("Hello world!")
|
||||
} label: {
|
||||
Text("ALT")
|
||||
}
|
||||
}.coordinateSpace(name: "preview")
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
//
|
||||
// MediaAltTextOverlay.swift
|
||||
//
|
||||
//
|
||||
// Created by Jed Fox on 2022-12-20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MediaAltTextOverlay: View {
|
||||
var altDescription: String?
|
||||
|
||||
@State private var showingAlt = false
|
||||
@Namespace private var namespace
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geom in
|
||||
ZStack {
|
||||
if let altDescription {
|
||||
if showingAlt {
|
||||
HStack(alignment: .top) {
|
||||
Text(altDescription)
|
||||
Spacer()
|
||||
Button(action: { showingAlt = false }) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
}
|
||||
.padding(8)
|
||||
.matchedGeometryEffect(id: "background", in: namespace, properties: .position)
|
||||
.transition(
|
||||
.scale(scale: 0.2, anchor: .bottomLeading)
|
||||
.combined(with: .opacity)
|
||||
)
|
||||
} else {
|
||||
Button("ALT") { showingAlt = true }
|
||||
.font(.caption.weight(.semibold))
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 3)
|
||||
.matchedGeometryEffect(id: "background", in: namespace, properties: .position)
|
||||
.transition(
|
||||
.scale(scale: 3, anchor: .trailing)
|
||||
.combined(with: .opacity)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.tint(.white)
|
||||
.background(Color.black.opacity(0.85))
|
||||
.cornerRadius(4)
|
||||
.overlay(
|
||||
.white.opacity(0.5),
|
||||
in: RoundedRectangle(cornerRadius: 4)
|
||||
.inset(by: -0.5)
|
||||
.stroke(lineWidth: 0.5)
|
||||
)
|
||||
.animation(.spring(response: 0.3), value: showingAlt)
|
||||
.frame(width: geom.size.width, height: geom.size.height, alignment: .bottomLeading)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 8)
|
||||
.onChange(of: altDescription) { _ in
|
||||
showingAlt = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MediaAltTextOverlay_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MediaAltTextOverlay(altDescription: "Hello, world!")
|
||||
.frame(height: 300)
|
||||
.background(Color.gray)
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MediaBadge<Content: View>: View {
|
||||
private let content: Content
|
||||
|
||||
init(@ViewBuilder content: () -> Content) {
|
||||
self.content = content()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
// need the VStack (or some other kind of containing view) to
|
||||
// ensure the transition animations work properly
|
||||
// Is this a bug? Is it intended behavior? I have no clue
|
||||
HStack {
|
||||
content
|
||||
}
|
||||
.font(.subheadline.bold())
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 2)
|
||||
.foregroundColor(.white)
|
||||
.tint(.white)
|
||||
.background(Color.black.opacity(0.7))
|
||||
.cornerRadius(3)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
extension MediaBadge where Content == Text {
|
||||
init(_ text: String) {
|
||||
self.init {
|
||||
Text(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MediaBadge_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MediaBadge {
|
||||
Button("ALT") {}
|
||||
}
|
||||
|
||||
MediaBadge {
|
||||
Button("GIF") {}
|
||||
}
|
||||
|
||||
MediaBadge {
|
||||
Text("01:24")
|
||||
.monospacedDigit()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
//
|
||||
// MediaBadgesContainer.swift
|
||||
//
|
||||
// Created by Jed Fox on 2022-12-20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MediaBadgesContainer: View {
|
||||
var altDescription: String?
|
||||
var isGIF = false
|
||||
var showDuration = false
|
||||
var mediaDuration: TimeInterval?
|
||||
|
||||
@State private var showingAlt = false
|
||||
@State private var space = AnyHashable(UUID())
|
||||
|
||||
// Date.ComponentsFormatStyle does not allow force-enabling minutes unit
|
||||
static let formatter: DateComponentsFormatter = {
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.allowedUnits = [.minute, .second]
|
||||
formatter.unitsStyle = .positional
|
||||
formatter.zeroFormattingBehavior = []
|
||||
formatter.formattingContext = .standalone
|
||||
return formatter
|
||||
}()
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geom in
|
||||
HStack(alignment: .bottom, spacing: 2) {
|
||||
if let altDescription {
|
||||
ExpandableMediaBadge("ALT", isExpanded: $showingAlt, in: (geom.size, space)) {
|
||||
Text(altDescription)
|
||||
.frame(maxHeight: geom.size.height - 16)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
if isGIF {
|
||||
MediaBadge("GIF")
|
||||
}
|
||||
if showDuration {
|
||||
if let mediaDuration, let format = Self.formatter.string(from: mediaDuration) {
|
||||
MediaBadge(format)
|
||||
.monospacedDigit()
|
||||
} else {
|
||||
MediaBadge("--:--")
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: geom.size.width, height: geom.size.height, alignment: .bottomLeading)
|
||||
.coordinateSpace(name: space)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 8)
|
||||
.onChange(of: altDescription) { _ in
|
||||
showingAlt = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MediaAltTextOverlay_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MediaBadgesContainer(altDescription: "Hello, world!")
|
||||
.frame(height: 300)
|
||||
.background(Color.gray)
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
|
@ -66,25 +66,8 @@ public final class MediaView: UIView {
|
|||
return wrapper
|
||||
}()
|
||||
|
||||
private(set) lazy var indicatorBlurEffectView: UIVisualEffectView = {
|
||||
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial))
|
||||
effectView.layer.masksToBounds = true
|
||||
effectView.layer.cornerCurve = .continuous
|
||||
effectView.layer.cornerRadius = 4
|
||||
return effectView
|
||||
}()
|
||||
private(set) lazy var indicatorVibrancyEffectView = UIVisualEffectView(
|
||||
effect: UIVibrancyEffect(blurEffect: UIBlurEffect(style: .systemUltraThinMaterial))
|
||||
)
|
||||
private(set) lazy var playerIndicatorLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .preferredFont(forTextStyle: .caption1)
|
||||
label.textColor = .secondaryLabel
|
||||
return label
|
||||
}()
|
||||
|
||||
let altViewController: UIHostingController<MediaAltTextOverlay> = {
|
||||
let vc = UIHostingController(rootView: MediaAltTextOverlay())
|
||||
let badgeViewController: UIHostingController<MediaBadgesContainer> = {
|
||||
let vc = UIHostingController(rootView: MediaBadgesContainer())
|
||||
vc.view.backgroundColor = .clear
|
||||
return vc
|
||||
}()
|
||||
|
@ -179,14 +162,14 @@ extension MediaView {
|
|||
playerViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
container.addSubview(playerViewController.view)
|
||||
playerViewController.view.pinToParent()
|
||||
|
||||
setupIndicatorViewHierarchy()
|
||||
playerIndicatorLabel.attributedText = NSAttributedString(string: "GIF")
|
||||
|
||||
|
||||
layoutAlt()
|
||||
}
|
||||
|
||||
private func bindGIF(configuration: Configuration, info: Configuration.VideoInfo) {
|
||||
badgeViewController.rootView.mediaDuration = info.durationMS.map { Double($0) / 1000 }
|
||||
badgeViewController.rootView.showDuration = false
|
||||
|
||||
guard let player = setupGIFPlayer(info: info) else { return }
|
||||
setupPlayerLooper(player: player)
|
||||
playerViewController.player = player
|
||||
|
@ -195,6 +178,8 @@ extension MediaView {
|
|||
// auto play for GIF
|
||||
player.play()
|
||||
|
||||
badgeViewController.rootView.isGIF = true
|
||||
|
||||
bindAlt(configuration: configuration, altDescription: info.altDescription)
|
||||
}
|
||||
|
||||
|
@ -212,6 +197,9 @@ extension MediaView {
|
|||
}
|
||||
|
||||
private func bindVideo(configuration: Configuration, info: Configuration.VideoInfo) {
|
||||
badgeViewController.rootView.mediaDuration = info.durationMS.map { Double($0) / 1000 }
|
||||
badgeViewController.rootView.showDuration = true
|
||||
|
||||
let imageInfo = Configuration.ImageInfo(
|
||||
aspectRadio: info.aspectRadio,
|
||||
assetURL: info.previewURL,
|
||||
|
@ -231,7 +219,7 @@ extension MediaView {
|
|||
accessibilityLabel = altDescription
|
||||
}
|
||||
|
||||
altViewController.rootView.altDescription = altDescription
|
||||
badgeViewController.rootView.altDescription = altDescription
|
||||
}
|
||||
|
||||
private func layoutBlurhash() {
|
||||
|
@ -263,9 +251,9 @@ extension MediaView {
|
|||
}
|
||||
|
||||
private func layoutAlt() {
|
||||
altViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
container.addSubview(altViewController.view)
|
||||
altViewController.view.pinToParent()
|
||||
badgeViewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
container.addSubview(badgeViewController.view)
|
||||
badgeViewController.view.pinToParent()
|
||||
}
|
||||
|
||||
public func prepareForReuse() {
|
||||
|
@ -295,15 +283,15 @@ extension MediaView {
|
|||
blurhashImageView.removeFromSuperview()
|
||||
blurhashImageView.removeConstraints(blurhashImageView.constraints)
|
||||
blurhashImageView.image = nil
|
||||
|
||||
// reset indicator
|
||||
indicatorBlurEffectView.removeFromSuperview()
|
||||
|
||||
|
||||
// reset container
|
||||
container.removeFromSuperview()
|
||||
container.removeConstraints(container.constraints)
|
||||
|
||||
altViewController.rootView.altDescription = nil
|
||||
badgeViewController.rootView.altDescription = nil
|
||||
badgeViewController.rootView.isGIF = false
|
||||
badgeViewController.rootView.showDuration = false
|
||||
badgeViewController.rootView.mediaDuration = nil
|
||||
|
||||
// reset configuration
|
||||
configuration = nil
|
||||
|
@ -333,36 +321,4 @@ extension MediaView {
|
|||
addSubview(container)
|
||||
container.pinToParent()
|
||||
}
|
||||
|
||||
private func setupIndicatorViewHierarchy() {
|
||||
let blurEffectView = indicatorBlurEffectView
|
||||
let vibrancyEffectView = indicatorVibrancyEffectView
|
||||
|
||||
assert(playerViewController.contentOverlayView != nil)
|
||||
if let contentOverlayView = playerViewController.contentOverlayView {
|
||||
blurEffectView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentOverlayView.addSubview(indicatorBlurEffectView)
|
||||
NSLayoutConstraint.activate([
|
||||
contentOverlayView.trailingAnchor.constraint(equalTo: blurEffectView.trailingAnchor, constant: 16),
|
||||
contentOverlayView.bottomAnchor.constraint(equalTo: blurEffectView.bottomAnchor, constant: 8),
|
||||
])
|
||||
}
|
||||
|
||||
if vibrancyEffectView.superview == nil {
|
||||
vibrancyEffectView.translatesAutoresizingMaskIntoConstraints = false
|
||||
blurEffectView.contentView.addSubview(vibrancyEffectView)
|
||||
vibrancyEffectView.pinToParent()
|
||||
}
|
||||
|
||||
if playerIndicatorLabel.superview == nil {
|
||||
playerIndicatorLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
vibrancyEffectView.contentView.addSubview(playerIndicatorLabel)
|
||||
NSLayoutConstraint.activate([
|
||||
playerIndicatorLabel.topAnchor.constraint(equalTo: vibrancyEffectView.contentView.topAnchor),
|
||||
playerIndicatorLabel.leadingAnchor.constraint(equalTo: vibrancyEffectView.contentView.leadingAnchor, constant: 3),
|
||||
vibrancyEffectView.contentView.trailingAnchor.constraint(equalTo: playerIndicatorLabel.trailingAnchor, constant: 3),
|
||||
playerIndicatorLabel.bottomAnchor.constraint(equalTo: vibrancyEffectView.contentView.bottomAnchor),
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue