mirror of
https://github.com/signalapp/Signal-iOS.git
synced 2025-12-05 01:10:41 +00:00
Remove stories code that’s no longer necessary
This commit is contained in:
@@ -892,7 +892,6 @@
|
||||
663D02DD2C06717900350632 /* UInt64SafeRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663D02DC2C06717900350632 /* UInt64SafeRecord.swift */; };
|
||||
663D02DF2C069AB600350632 /* OrphanedAttachmentCleanerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663D02DE2C069AB600350632 /* OrphanedAttachmentCleanerTest.swift */; };
|
||||
663D02E12C06E2F400350632 /* MockAttachmentReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663D02E02C06E2F400350632 /* MockAttachmentReference.swift */; };
|
||||
663D6A7C292319BC00CABC49 /* ConversationPickerFailedRecipientsSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663D6A7B292319BC00CABC49 /* ConversationPickerFailedRecipientsSheet.swift */; };
|
||||
663F81D32E789E240033D2AE /* BackupAttachmentCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663F81D22E789E0D0033D2AE /* BackupAttachmentCoordinator.swift */; };
|
||||
663F81D52E78A70C0033D2AE /* BackupAttachmentDownloadScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663F81D42E78A6F10033D2AE /* BackupAttachmentDownloadScheduler.swift */; };
|
||||
663F81D72E78BB9C0033D2AE /* BackupListMediaStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 663F81D62E78B5950033D2AE /* BackupListMediaStore.swift */; };
|
||||
@@ -4874,7 +4873,6 @@
|
||||
663D02DC2C06717900350632 /* UInt64SafeRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UInt64SafeRecord.swift; sourceTree = "<group>"; };
|
||||
663D02DE2C069AB600350632 /* OrphanedAttachmentCleanerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrphanedAttachmentCleanerTest.swift; sourceTree = "<group>"; };
|
||||
663D02E02C06E2F400350632 /* MockAttachmentReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAttachmentReference.swift; sourceTree = "<group>"; };
|
||||
663D6A7B292319BC00CABC49 /* ConversationPickerFailedRecipientsSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationPickerFailedRecipientsSheet.swift; sourceTree = "<group>"; };
|
||||
663F81D22E789E0D0033D2AE /* BackupAttachmentCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupAttachmentCoordinator.swift; sourceTree = "<group>"; };
|
||||
663F81D42E78A6F10033D2AE /* BackupAttachmentDownloadScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupAttachmentDownloadScheduler.swift; sourceTree = "<group>"; };
|
||||
663F81D62E78B5950033D2AE /* BackupListMediaStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupListMediaStore.swift; sourceTree = "<group>"; };
|
||||
@@ -10987,7 +10985,6 @@
|
||||
34A95536271B510400B05242 /* ContactTableViewCell.swift */,
|
||||
34A9557C271B510500B05242 /* ConversationItem.swift */,
|
||||
34A9557B271B510500B05242 /* ConversationPicker.swift */,
|
||||
663D6A7B292319BC00CABC49 /* ConversationPickerFailedRecipientsSheet.swift */,
|
||||
34ACA7F52733183000E47AD4 /* CountryCodeViewController.swift */,
|
||||
E1C2A54A2A8FCB0D00AEC4DA /* DeleteSystemContactViewController.swift */,
|
||||
34A9557D271B510500B05242 /* FindByPhoneNumberViewController.swift */,
|
||||
@@ -16601,7 +16598,6 @@
|
||||
3402AA75271D9E180084CBAE /* ConversationAvatarView.swift in Sources */,
|
||||
3402AA37271D9DCD0084CBAE /* ConversationItem.swift in Sources */,
|
||||
3402AA33271D9DCD0084CBAE /* ConversationPicker.swift in Sources */,
|
||||
663D6A7C292319BC00CABC49 /* ConversationPickerFailedRecipientsSheet.swift in Sources */,
|
||||
3402AA03271D9DB50084CBAE /* ConversationStyle.swift in Sources */,
|
||||
34ACA7F72733183000E47AD4 /* CountryCodeViewController.swift in Sources */,
|
||||
508F0346296F72F4001D88D0 /* CustomCellBackgroundColor.swift in Sources */,
|
||||
|
||||
@@ -8911,21 +8911,6 @@
|
||||
/* Context menu action to share the selected story */
|
||||
"STORIES_SHARE_STORY_ACTION" = "Share";
|
||||
|
||||
/* Section title shown when sending to non-story conversations but failing to send the file to stories. */
|
||||
"STORIES_SHARESHEET_PARTIAL_SEND_REMAINING_SECTION_TITLE" = "Sent To";
|
||||
|
||||
/* Section title shown when failing to send an incompatible file to stories, but still sending to non-story conversations. */
|
||||
"STORIES_SHARESHEET_PARTIAL_SEND_STORIES_SECTION_TITLE" = "Not Sent To";
|
||||
|
||||
/* Title shown when failing to send an incompatible file to stories, but still sending to non-story conversations. */
|
||||
"STORIES_SHARESHEET_PARTIAL_SEND_TITLE" = "Partially Sent";
|
||||
|
||||
/* Subtitle shown when failing to send an incompatible file to stories via the sharesheet. */
|
||||
"STORIES_SHARESHEET_UNABLE_TO_SEND_SEND_SUBTITLE" = "The file type selected is not supported.";
|
||||
|
||||
/* Title shown when failing to send an incompatible file to stories via the sharesheet. */
|
||||
"STORIES_SHARESHEET_UNABLE_TO_SEND_SEND_TITLE" = "Unable To Send";
|
||||
|
||||
/* Title for the stories view. */
|
||||
"STORIES_TITLE" = "Stories";
|
||||
|
||||
|
||||
@@ -1448,22 +1448,6 @@
|
||||
<string>%d attempts</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>STORIES_SHARESHEET_PARTIAL_SEND_SUBTITLE_%d</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%1$#@text@</string>
|
||||
<key>text</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Not all items could be sent to the story you selected because the file type is not supported.</string>
|
||||
<key>other</key>
|
||||
<string>Not all items could be sent to the stories you selected because the file type is not supported.</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>STORY_REPLIES_COUNT_%d</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
|
||||
@@ -381,6 +381,10 @@ public class SignalAttachment: CustomDebugStringConvertible {
|
||||
return self.isVideo ? (self.dataSource as! DataSourcePath) : nil
|
||||
}
|
||||
|
||||
public var isVisualMedia: Bool {
|
||||
return self.isImage || self.isVideo
|
||||
}
|
||||
|
||||
public var isAudio: Bool {
|
||||
return SignalAttachment.audioUTISet.contains(dataUTI)
|
||||
}
|
||||
|
||||
@@ -388,10 +388,7 @@ public class ShareViewController: OWSNavigationController, ShareViewDelegate, SA
|
||||
|
||||
setProgress(progress)
|
||||
|
||||
let typedItems = try await self.buildAttachments(
|
||||
for: itemsAndProgresses,
|
||||
mustBeVisualMedia: itemsAndProgresses.count >= 2,
|
||||
)
|
||||
let typedItems = try await self.buildAttachments(for: itemsAndProgresses)
|
||||
try Task.checkCancellation()
|
||||
|
||||
// Make sure the user is not trying to share more than our attachment limit.
|
||||
@@ -456,14 +453,11 @@ public class ShareViewController: OWSNavigationController, ShareViewDelegate, SA
|
||||
throw ShareViewControllerError.noConformingInputItem
|
||||
}
|
||||
|
||||
private nonisolated func buildAttachments(
|
||||
for itemsAndProgresses: [(TypedItemProvider, Progress)],
|
||||
mustBeVisualMedia: Bool,
|
||||
) async throws -> [TypedItem] {
|
||||
private nonisolated func buildAttachments(for itemsAndProgresses: [(TypedItemProvider, Progress)]) async throws -> [TypedItem] {
|
||||
// FIXME: does not use a task group because SignalAttachment likes to load things into RAM and resize them; doing this in parallel can exhaust available RAM
|
||||
var result: [TypedItem] = []
|
||||
for (typedItemProvider, progress) in itemsAndProgresses {
|
||||
result.append(try await typedItemProvider.buildAttachment(mustBeVisualMedia: mustBeVisualMedia, progress: progress))
|
||||
result.append(try await typedItemProvider.buildAttachment(progress: progress))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -554,31 +554,6 @@ extension SharingThreadPickerViewController: ConversationPickerDelegate {
|
||||
}
|
||||
|
||||
func conversationPickerDidCompleteSelection(_ conversationPickerViewController: ConversationPickerViewController) {
|
||||
// Check if the attachments are compatible with sending to stories.
|
||||
let storySelections = selection.conversations.compactMap({ $0 as? StoryConversationItem })
|
||||
if !storySelections.isEmpty {
|
||||
if !self.canSendTypedItemsToStory() {
|
||||
// Can't send to stories!
|
||||
storySelections.forEach { self.selection.remove($0) }
|
||||
self.updateUIForCurrentSelection(animated: false)
|
||||
self.tableView.reloadData()
|
||||
let vc = ConversationPickerFailedRecipientsSheet(
|
||||
failedStoryConversationItems: storySelections,
|
||||
remainingConversationItems: self.selection.conversations,
|
||||
onApprove: { [weak self] in
|
||||
guard
|
||||
let strongSelf = self,
|
||||
strongSelf.selection.conversations.isEmpty.negated
|
||||
else {
|
||||
return
|
||||
}
|
||||
strongSelf.conversationPickerDidCompleteSelection(strongSelf)
|
||||
})
|
||||
self.present(vc, animated: true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
approve()
|
||||
}
|
||||
|
||||
|
||||
@@ -43,17 +43,15 @@ public enum TypedItem {
|
||||
public var isVisualMedia: Bool {
|
||||
switch self {
|
||||
case .text, .contact: false
|
||||
case .other(let attachment): attachment.isImage || attachment.isVideo
|
||||
case .other(let attachment): attachment.isVisualMedia
|
||||
}
|
||||
}
|
||||
|
||||
public var isStoriesCompatible: Bool {
|
||||
switch self {
|
||||
case .text: return true
|
||||
case .contact: return false
|
||||
case .other(let attachment):
|
||||
// TODO: Consolidate with isVisualMedia after fixing validity checks.
|
||||
return attachment.isImage || attachment.isVideo
|
||||
case .text: true
|
||||
case .contact: false
|
||||
case .other(let attachment): attachment.isVisualMedia
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,12 +151,12 @@ public struct TypedItemProvider {
|
||||
private static let itemTypeOrder: [TypedItemProvider.ItemType] = [.movie, .image, .contact, .json, .plainText, .text, .pdf, .pkPass, .fileUrl, .webUrl, .data]
|
||||
|
||||
public static func buildVisualMediaAttachment(forItemProvider itemProvider: NSItemProvider) async throws -> SignalAttachment {
|
||||
let typedItem = try await make(for: itemProvider).buildAttachment(mustBeVisualMedia: true)
|
||||
let typedItem = try await make(for: itemProvider).buildAttachment()
|
||||
switch typedItem {
|
||||
case .text, .contact:
|
||||
owsFail("not possible because mustBeVisualMedia is true")
|
||||
case .other(let attachment):
|
||||
case .other(let attachment) where attachment.isVisualMedia:
|
||||
return attachment
|
||||
case .text, .contact, .other:
|
||||
throw SignalAttachmentError.invalidFileFormat
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +179,7 @@ public struct TypedItemProvider {
|
||||
|
||||
// MARK: Methods
|
||||
|
||||
public nonisolated func buildAttachment(mustBeVisualMedia: Bool, progress: Progress? = nil) async throws -> TypedItem {
|
||||
public nonisolated func buildAttachment(progress: Progress? = nil) async throws -> TypedItem {
|
||||
// Whenever this finishes, mark its progress as fully complete. This
|
||||
// handles item providers that can't provide partial progress updates.
|
||||
defer {
|
||||
@@ -200,7 +198,7 @@ public struct TypedItemProvider {
|
||||
// 2) try to load a UIImage directly in the case that is what was sent over
|
||||
// 3) try to NSKeyedUnarchive NSData directly into a UIImage
|
||||
do {
|
||||
attachment = try await buildFileAttachment(mustBeVisualMedia: mustBeVisualMedia, progress: progress)
|
||||
attachment = try await buildFileAttachment(mustBeVisualMedia: true, progress: progress)
|
||||
} catch SignalAttachmentError.couldNotParseImage, ItemProviderError.fileUrlWasBplist {
|
||||
Logger.warn("failed to parse image directly from file; checking for loading UIImage directly")
|
||||
let image: UIImage = try await loadObjectWithKeyedUnarchiverFallback(
|
||||
@@ -209,8 +207,10 @@ public struct TypedItemProvider {
|
||||
)
|
||||
attachment = try Self.createAttachment(withImage: image)
|
||||
}
|
||||
case .movie, .pdf, .data:
|
||||
attachment = try await self.buildFileAttachment(mustBeVisualMedia: mustBeVisualMedia, progress: progress)
|
||||
case .movie:
|
||||
attachment = try await self.buildFileAttachment(mustBeVisualMedia: true, progress: progress)
|
||||
case .pdf, .data:
|
||||
attachment = try await self.buildFileAttachment(mustBeVisualMedia: false, progress: progress)
|
||||
case .fileUrl, .json:
|
||||
let url: NSURL = try await loadObjectWithKeyedUnarchiverFallback(
|
||||
overrideTypeIdentifier: TypedItemProvider.ItemType.fileUrl.typeIdentifier,
|
||||
@@ -224,37 +224,25 @@ public struct TypedItemProvider {
|
||||
attachment = try await _buildFileAttachment(
|
||||
dataSource: dataSource,
|
||||
dataUTI: dataUTI,
|
||||
mustBeVisualMedia: mustBeVisualMedia,
|
||||
mustBeVisualMedia: false,
|
||||
progress: progress,
|
||||
)
|
||||
case .webUrl:
|
||||
if mustBeVisualMedia {
|
||||
throw SignalAttachmentError.invalidFileFormat
|
||||
}
|
||||
let url: NSURL = try await loadObjectWithKeyedUnarchiverFallback(
|
||||
cannotLoadError: .cannotLoadURLObject,
|
||||
failedLoadError: .loadURLObjectFailed
|
||||
)
|
||||
return try Self.createAttachment(withText: (url as URL).absoluteString)
|
||||
case .contact:
|
||||
if mustBeVisualMedia {
|
||||
throw SignalAttachmentError.invalidFileFormat
|
||||
}
|
||||
let contactData = try await loadDataRepresentation()
|
||||
return .contact(contactData)
|
||||
case .plainText, .text:
|
||||
if mustBeVisualMedia {
|
||||
throw SignalAttachmentError.invalidFileFormat
|
||||
}
|
||||
let text: NSString = try await loadObjectWithKeyedUnarchiverFallback(
|
||||
cannotLoadError: .cannotLoadStringObject,
|
||||
failedLoadError: .loadStringObjectFailed
|
||||
)
|
||||
return try Self.createAttachment(withText: text as String)
|
||||
case .pkPass:
|
||||
if mustBeVisualMedia {
|
||||
throw SignalAttachmentError.invalidFileFormat
|
||||
}
|
||||
let pkPass = try await loadDataRepresentation()
|
||||
let dataSource = DataSourceValue(pkPass, utiType: itemType.typeIdentifier)
|
||||
guard let dataSource else {
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
//
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SignalServiceKit
|
||||
|
||||
public class ConversationPickerFailedRecipientsSheet: OWSTableSheetViewController {
|
||||
|
||||
let failedStoryConversationItems: [StoryConversationItem]
|
||||
let remainingConversationItems: [ConversationItem]
|
||||
let onApprove: () -> Void
|
||||
|
||||
public init(
|
||||
failedStoryConversationItems: [StoryConversationItem],
|
||||
remainingConversationItems: [ConversationItem],
|
||||
onApprove: @escaping () -> Void
|
||||
) {
|
||||
assert(!failedStoryConversationItems.isEmpty)
|
||||
self.failedStoryConversationItems = failedStoryConversationItems
|
||||
self.remainingConversationItems = remainingConversationItems
|
||||
self.onApprove = onApprove
|
||||
super.init()
|
||||
|
||||
tableViewController.bottomFooter = tableFooterView()
|
||||
}
|
||||
|
||||
public override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
tableViewController.tableView.register(
|
||||
ConversationPickerCell.self,
|
||||
forCellReuseIdentifier: ConversationPickerCell.reuseIdentifier
|
||||
)
|
||||
}
|
||||
|
||||
private func tableFooterView() -> UIView? {
|
||||
let doneButton = UIButton(
|
||||
configuration: .largePrimary(title: CommonStrings.okayButton),
|
||||
primaryAction: UIAction { [weak self] _ in
|
||||
self?.dismiss(animated: true) {
|
||||
self?.onApprove()
|
||||
}
|
||||
}
|
||||
)
|
||||
let stackView = doneButton.enclosedInVerticalStackView(isFullWidthButton: true)
|
||||
stackView.directionalLayoutMargins.leading = 48
|
||||
stackView.directionalLayoutMargins.trailing = 48
|
||||
return stackView
|
||||
}
|
||||
|
||||
public override func tableContents() -> OWSTableContents {
|
||||
let contents = OWSTableContents()
|
||||
|
||||
let headerSection = OWSTableSection()
|
||||
headerSection.separatorInsetLeading = OWSTableViewController2.cellHInnerMargin + 32
|
||||
|
||||
let headerTitle: String
|
||||
let headerSubtitle: String
|
||||
if remainingConversationItems.isEmpty {
|
||||
headerTitle = OWSLocalizedString(
|
||||
"STORIES_SHARESHEET_UNABLE_TO_SEND_SEND_TITLE",
|
||||
comment: "Title shown when failing to send an incompatible file to stories via the sharesheet."
|
||||
)
|
||||
headerSubtitle = OWSLocalizedString(
|
||||
"STORIES_SHARESHEET_UNABLE_TO_SEND_SEND_SUBTITLE",
|
||||
comment: "Subtitle shown when failing to send an incompatible file to stories via the sharesheet."
|
||||
)
|
||||
} else {
|
||||
headerTitle = OWSLocalizedString(
|
||||
"STORIES_SHARESHEET_PARTIAL_SEND_TITLE",
|
||||
comment: "Title shown when failing to send an incompatible file to stories, but still sending to non-story conversations."
|
||||
)
|
||||
let subtitleFormat = OWSLocalizedString(
|
||||
"STORIES_SHARESHEET_PARTIAL_SEND_SUBTITLE_%d",
|
||||
tableName: "PluralAware",
|
||||
comment: "Subtitle shown when failing to send a single incompatible file to stories via the sharesheet."
|
||||
)
|
||||
headerSubtitle = String.localizedStringWithFormat(
|
||||
subtitleFormat,
|
||||
failedStoryConversationItems.count
|
||||
)
|
||||
}
|
||||
|
||||
let headerView = SheetHeaderView(
|
||||
title: headerTitle,
|
||||
subtitle: headerSubtitle
|
||||
)
|
||||
headerSection.customHeaderView = headerView
|
||||
contents.add(headerSection)
|
||||
|
||||
let failedStoriesSection = OWSTableSection()
|
||||
failedStoriesSection.headerTitle = OWSLocalizedString(
|
||||
"STORIES_SHARESHEET_PARTIAL_SEND_STORIES_SECTION_TITLE",
|
||||
comment: "Section title shown when failing to send an incompatible file to stories, but still sending to non-story conversations."
|
||||
)
|
||||
for item in failedStoryConversationItems {
|
||||
failedStoriesSection.add(OWSTableItem(dequeueCellBlock: { tableView in
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: ConversationPickerCell.reuseIdentifier) as? ConversationPickerCell else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
SSKEnvironment.shared.databaseStorageRef.read {
|
||||
cell.configure(conversationItem: item, transaction: $0)
|
||||
}
|
||||
cell.showsSelectionUI = false
|
||||
return cell
|
||||
}))
|
||||
}
|
||||
contents.add(failedStoriesSection)
|
||||
|
||||
if !remainingConversationItems.isEmpty {
|
||||
let remainingConversationsSection = OWSTableSection()
|
||||
remainingConversationsSection.headerTitle = OWSLocalizedString(
|
||||
"STORIES_SHARESHEET_PARTIAL_SEND_REMAINING_SECTION_TITLE",
|
||||
comment: "Section title shown when sending to non-story conversations but failing to send the file to stories."
|
||||
)
|
||||
for item in remainingConversationItems {
|
||||
remainingConversationsSection.add(OWSTableItem(dequeueCellBlock: { tableView in
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: ConversationPickerCell.reuseIdentifier) as? ConversationPickerCell else {
|
||||
return UITableViewCell()
|
||||
}
|
||||
SSKEnvironment.shared.databaseStorageRef.read {
|
||||
cell.configure(conversationItem: item, transaction: $0)
|
||||
}
|
||||
cell.showsSelectionUI = false
|
||||
return cell
|
||||
}))
|
||||
}
|
||||
contents.add(remainingConversationsSection)
|
||||
}
|
||||
|
||||
return contents
|
||||
}
|
||||
|
||||
private class SheetHeaderView: UIView {
|
||||
|
||||
let titleLabel = UILabel()
|
||||
let subtitleLabel = UILabel()
|
||||
|
||||
init(title: String, subtitle: String) {
|
||||
super.init(frame: .zero)
|
||||
|
||||
titleLabel.text = title
|
||||
titleLabel.textAlignment = .center
|
||||
titleLabel.font = .dynamicTypeTitle2.semibold()
|
||||
titleLabel.textColor = Theme.primaryTextColor
|
||||
addSubview(titleLabel)
|
||||
|
||||
subtitleLabel.numberOfLines = 0
|
||||
subtitleLabel.text = subtitle
|
||||
subtitleLabel.textAlignment = .center
|
||||
subtitleLabel.font = .dynamicTypeSubheadline
|
||||
subtitleLabel.textColor = Theme.primaryTextColor
|
||||
addSubview(subtitleLabel)
|
||||
|
||||
titleLabel.autoPinWidthToSuperview(withMargin: 24)
|
||||
titleLabel.autoPinTopToSuperviewMargin()
|
||||
|
||||
subtitleLabel.autoHCenterInSuperview()
|
||||
subtitleLabel.autoPinWidthToSuperview(withMargin: 24)
|
||||
subtitleLabel.autoPinBottomToSuperviewMargin()
|
||||
subtitleLabel.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: 8)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user