Add context menu for Fullscreen mode actions. Issue #1344
This commit is contained in:
parent
b1471d4d20
commit
323b160b7f
@ -151,6 +151,7 @@
|
||||
51B62E68233186730085F949 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* IconView.swift */; };
|
||||
51BB7C272335A8E5008E8144 /* ArticleActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */; };
|
||||
51BB7C312335ACDE008E8144 /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = 51BB7C302335ACDE008E8144 /* page.html */; };
|
||||
51C266EA238C334800F53014 /* ContextMenuPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C266E9238C334800F53014 /* ContextMenuPreviewViewController.swift */; };
|
||||
51C451A9226377C200C03939 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; };
|
||||
51C451AA226377C200C03939 /* ArticlesDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8407167F2262A61100344432 /* ArticlesDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
51C451B9226377C900C03939 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 840716732262A60F00344432 /* Articles.framework */; };
|
||||
@ -1321,6 +1322,7 @@
|
||||
51B62E67233186730085F949 /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = "<group>"; };
|
||||
51BB7C262335A8E5008E8144 /* ArticleActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleActivityItemSource.swift; sourceTree = "<group>"; };
|
||||
51BB7C302335ACDE008E8144 /* page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = page.html; sourceTree = "<group>"; };
|
||||
51C266E9238C334800F53014 /* ContextMenuPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuPreviewViewController.swift; sourceTree = "<group>"; };
|
||||
51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = "<group>"; };
|
||||
51C45250226506F400C03939 /* String-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-Extensions.swift"; sourceTree = "<group>"; };
|
||||
51C45254226507D200C03939 /* AppAssets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = "<group>"; };
|
||||
@ -1948,6 +1950,7 @@
|
||||
51C4527E2265092C00C03939 /* ArticleViewController.swift */,
|
||||
517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */,
|
||||
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */,
|
||||
51C266E9238C334800F53014 /* ContextMenuPreviewViewController.swift */,
|
||||
5142192923522B5500E07E2C /* ImageViewController.swift */,
|
||||
514219362352510100E07E2C /* ImageScrollView.swift */,
|
||||
518651D9235621840078E021 /* ImageTransition.swift */,
|
||||
@ -2963,7 +2966,7 @@
|
||||
};
|
||||
513C5CE5232571C2003D4054 = {
|
||||
CreatedOnToolsVersion = 11.0;
|
||||
DevelopmentTeam = 8EQFQ9RY84;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
518B2ED12351B3DD00400001 = {
|
||||
@ -2973,7 +2976,7 @@
|
||||
TestTargetID = 840D617B2029031C009BC708;
|
||||
};
|
||||
6581C73220CED60000F4AD34 = {
|
||||
DevelopmentTeam = 8EQFQ9RY84;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
65ED3FA2235DEF6C0081F399 = {
|
||||
@ -2986,7 +2989,7 @@
|
||||
};
|
||||
840D617B2029031C009BC708 = {
|
||||
CreatedOnToolsVersion = 9.3;
|
||||
DevelopmentTeam = 8EQFQ9RY84;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.BackgroundModes = {
|
||||
@ -2996,7 +2999,7 @@
|
||||
};
|
||||
849C645F1ED37A5D003D8FC0 = {
|
||||
CreatedOnToolsVersion = 8.2.1;
|
||||
DevelopmentTeam = 8EQFQ9RY84;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.HardenedRuntime = {
|
||||
@ -3006,7 +3009,7 @@
|
||||
};
|
||||
849C64701ED37A5D003D8FC0 = {
|
||||
CreatedOnToolsVersion = 8.2.1;
|
||||
DevelopmentTeam = 8EQFQ9RY84;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
ProvisioningStyle = Automatic;
|
||||
TestTargetID = 849C645F1ED37A5D003D8FC0;
|
||||
};
|
||||
@ -4027,6 +4030,7 @@
|
||||
51934CCE2310792F006127BE /* ActivityManager.swift in Sources */,
|
||||
5108F6B72375E612001ABC45 /* CacheCleaner.swift in Sources */,
|
||||
518651DA235621840078E021 /* ImageTransition.swift in Sources */,
|
||||
51C266EA238C334800F53014 /* ContextMenuPreviewViewController.swift in Sources */,
|
||||
51627A6923861DED007B3B4B /* MasterFeedViewController+Drop.swift in Sources */,
|
||||
514219372352510100E07E2C /* ImageScrollView.swift in Sources */,
|
||||
516AE9B32371C372007DEEAA /* MasterFeedTableViewSectionHeaderLayout.swift in Sources */,
|
||||
|
@ -92,6 +92,50 @@ extension Article {
|
||||
|
||||
return FaviconGenerator.favicon(webFeed)
|
||||
}
|
||||
|
||||
func byline() -> String {
|
||||
guard let authors = authors ?? webFeed?.authors, !authors.isEmpty else {
|
||||
return ""
|
||||
}
|
||||
|
||||
// If the author's name is the same as the feed, then we don't want to display it.
|
||||
// This code assumes that multiple authors would never match the feed name so that
|
||||
// if there feed owner has an article co-author all authors are given the byline.
|
||||
if authors.count == 1, let author = authors.first {
|
||||
if author.name == webFeed?.nameForDisplay {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
var byline = ""
|
||||
var isFirstAuthor = true
|
||||
|
||||
for author in authors {
|
||||
if !isFirstAuthor {
|
||||
byline += ", "
|
||||
}
|
||||
isFirstAuthor = false
|
||||
|
||||
if let emailAddress = author.emailAddress, emailAddress.contains(" ") {
|
||||
byline += emailAddress // probably name plus email address
|
||||
}
|
||||
else if let name = author.name, let emailAddress = author.emailAddress {
|
||||
byline += "\(name) <\(emailAddress)>"
|
||||
}
|
||||
else if let name = author.name {
|
||||
byline += name
|
||||
}
|
||||
else if let emailAddress = author.emailAddress {
|
||||
byline += "<\(emailAddress)>"
|
||||
}
|
||||
else if let url = author.url {
|
||||
byline += url
|
||||
}
|
||||
}
|
||||
|
||||
return byline
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Path
|
||||
|
@ -43,6 +43,10 @@ struct AppAssets {
|
||||
return UIImage(named: "articleExtractorOff")!
|
||||
}()
|
||||
|
||||
static var articleExtractorOffSmall: UIImage = {
|
||||
return UIImage(systemName: "doc.plaintext")!
|
||||
}()
|
||||
|
||||
static var articleExtractorOffTinted: UIImage = {
|
||||
let image = UIImage(named: "articleExtractorOff")!
|
||||
return image.maskWithColor(color: AppAssets.primaryAccentColor.cgColor)!
|
||||
@ -52,6 +56,10 @@ struct AppAssets {
|
||||
return UIImage(named: "articleExtractorOn")!
|
||||
}()
|
||||
|
||||
static var articleExtractorOnSmall: UIImage = {
|
||||
return UIImage(systemName: "doc.plaintext")!
|
||||
}()
|
||||
|
||||
static var articleExtractorOnTinted: UIImage = {
|
||||
let image = UIImage(named: "articleExtractorOn")!
|
||||
return image.maskWithColor(color: AppAssets.primaryAccentColor.cgColor)!
|
||||
@ -129,6 +137,18 @@ struct AppAssets {
|
||||
return UIImage(systemName: "ellipsis.circle")!
|
||||
}()
|
||||
|
||||
static var nextArticleImage: UIImage = {
|
||||
return UIImage(systemName: "chevron.down")!
|
||||
}()
|
||||
|
||||
static var nextUnreadArticleImage: UIImage = {
|
||||
return UIImage(systemName: "chevron.down.circle")!
|
||||
}()
|
||||
|
||||
static var prevArticleImage: UIImage = {
|
||||
return UIImage(systemName: "chevron.up")!
|
||||
}()
|
||||
|
||||
static var openInSidebarImage: UIImage = {
|
||||
return UIImage(systemName: "arrow.turn.down.left")!
|
||||
}()
|
||||
|
@ -47,6 +47,10 @@ class ArticleViewController: UIViewController {
|
||||
}()
|
||||
|
||||
private var webView: WKWebView!
|
||||
private lazy var contextMenuInteraction = UIContextMenuInteraction(delegate: self)
|
||||
private var isFullScreenAvailable: Bool {
|
||||
return traitCollection.userInterfaceIdiom == .phone && coordinator.isRootSplitCollapsed
|
||||
}
|
||||
private lazy var transition = ImageTransition(controller: self)
|
||||
private var clickedImageCompletion: (() -> Void)?
|
||||
|
||||
@ -93,6 +97,7 @@ class ArticleViewController: UIViewController {
|
||||
webView?.evaluateJavaScript("cancelImageLoad();")
|
||||
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked)
|
||||
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasShown)
|
||||
webView.removeInteraction(contextMenuInteraction)
|
||||
webView.removeFromSuperview()
|
||||
ArticleViewControllerWebViewProvider.shared.enqueueWebView(webView)
|
||||
webView = nil
|
||||
@ -122,6 +127,7 @@ class ArticleViewController: UIViewController {
|
||||
self.webViewContainer.addChildAndPin(webView)
|
||||
webView.navigationDelegate = self
|
||||
webView.uiDelegate = self
|
||||
self.configureContextMenuInteraction()
|
||||
|
||||
webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasClicked)
|
||||
webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasShown)
|
||||
@ -285,19 +291,8 @@ class ArticleViewController: UIViewController {
|
||||
coordinator.toggleStarredForCurrentArticle()
|
||||
}
|
||||
|
||||
@IBAction func openBrowser(_ sender: Any) {
|
||||
coordinator.showBrowserForCurrentArticle()
|
||||
}
|
||||
|
||||
@IBAction func showActivityDialog(_ sender: Any) {
|
||||
guard let preferredLink = currentArticle?.preferredLink, let url = URL(string: preferredLink) else {
|
||||
return
|
||||
}
|
||||
|
||||
let itemSource = ArticleActivityItemSource(url: url, subject: currentArticle!.title)
|
||||
let activityViewController = UIActivityViewController(activityItems: [itemSource], applicationActivities: nil)
|
||||
activityViewController.popoverPresentationController?.barButtonItem = actionBarButtonItem
|
||||
present(activityViewController, animated: true)
|
||||
showActivityDialog()
|
||||
}
|
||||
|
||||
// MARK: Keyboard Shortcuts
|
||||
@ -357,6 +352,39 @@ extension ArticleViewController: InteractiveNavigationControllerTappable {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: UIContextMenuInteractionDelegate
|
||||
|
||||
extension ArticleViewController: UIContextMenuInteractionDelegate {
|
||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
||||
|
||||
return UIContextMenuConfiguration(identifier: nil, previewProvider: contextMenuPreviewProvider) { [weak self] suggestedActions in
|
||||
guard let self = self else { return nil }
|
||||
var actions = [UIAction]()
|
||||
|
||||
if let action = self.prevArticleAction() {
|
||||
actions.append(action)
|
||||
}
|
||||
if let action = self.nextArticleAction() {
|
||||
actions.append(action)
|
||||
}
|
||||
actions.append(self.toggleReadAction())
|
||||
actions.append(self.toggleStarredAction())
|
||||
if let action = self.nextUnreadArticleAction() {
|
||||
actions.append(action)
|
||||
}
|
||||
actions.append(self.toggleArticleExtractorAction())
|
||||
actions.append(self.shareAction())
|
||||
|
||||
return UIMenu(title: "", children: actions)
|
||||
}
|
||||
}
|
||||
|
||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
||||
coordinator.showBrowserForCurrentArticle()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: WKNavigationDelegate
|
||||
|
||||
extension ArticleViewController: WKNavigationDelegate {
|
||||
@ -492,25 +520,113 @@ private extension ArticleViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func showActivityDialog() {
|
||||
guard let preferredLink = currentArticle?.preferredLink, let url = URL(string: preferredLink) else {
|
||||
return
|
||||
}
|
||||
|
||||
let itemSource = ArticleActivityItemSource(url: url, subject: currentArticle!.title)
|
||||
let activityViewController = UIActivityViewController(activityItems: [itemSource], applicationActivities: nil)
|
||||
activityViewController.popoverPresentationController?.barButtonItem = actionBarButtonItem
|
||||
present(activityViewController, animated: true)
|
||||
}
|
||||
|
||||
func showBars() {
|
||||
if traitCollection.userInterfaceIdiom == .phone && coordinator.isRootSplitCollapsed {
|
||||
if isFullScreenAvailable {
|
||||
AppDefaults.articleFullscreenEnabled = false
|
||||
coordinator.showStatusBar()
|
||||
showNavigationViewConstraint.constant = 0
|
||||
showToolbarViewConstraint.constant = 0
|
||||
navigationController?.setNavigationBarHidden(false, animated: true)
|
||||
navigationController?.setToolbarHidden(false, animated: true)
|
||||
configureContextMenuInteraction()
|
||||
}
|
||||
}
|
||||
|
||||
func hideBars() {
|
||||
if traitCollection.userInterfaceIdiom == .phone && coordinator.isRootSplitCollapsed {
|
||||
if isFullScreenAvailable {
|
||||
AppDefaults.articleFullscreenEnabled = true
|
||||
coordinator.hideStatusBar()
|
||||
showNavigationViewConstraint.constant = 44.0
|
||||
showToolbarViewConstraint.constant = 44.0
|
||||
navigationController?.setNavigationBarHidden(true, animated: true)
|
||||
navigationController?.setToolbarHidden(true, animated: true)
|
||||
configureContextMenuInteraction()
|
||||
}
|
||||
}
|
||||
|
||||
func configureContextMenuInteraction() {
|
||||
if isFullScreenAvailable {
|
||||
if navigationController?.isNavigationBarHidden ?? false {
|
||||
webView?.addInteraction(contextMenuInteraction)
|
||||
} else {
|
||||
webView?.removeInteraction(contextMenuInteraction)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func contextMenuPreviewProvider() -> UIViewController {
|
||||
let previewProvider = UIStoryboard.main.instantiateController(ofType: ContextMenuPreviewViewController.self)
|
||||
previewProvider.article = currentArticle
|
||||
return previewProvider
|
||||
}
|
||||
|
||||
func prevArticleAction() -> UIAction? {
|
||||
guard coordinator.isPrevArticleAvailable else { return nil }
|
||||
let title = NSLocalizedString("Previous Article", comment: "Previous Article")
|
||||
return UIAction(title: title, image: AppAssets.prevArticleImage) { [weak self] action in
|
||||
self?.coordinator.selectPrevArticle()
|
||||
}
|
||||
}
|
||||
|
||||
func nextArticleAction() -> UIAction? {
|
||||
guard coordinator.isNextArticleAvailable else { return nil }
|
||||
let title = NSLocalizedString("Next Article", comment: "Next Article")
|
||||
return UIAction(title: title, image: AppAssets.nextArticleImage) { [weak self] action in
|
||||
self?.coordinator.selectNextArticle()
|
||||
}
|
||||
}
|
||||
|
||||
func toggleReadAction() -> UIAction {
|
||||
let read = currentArticle?.status.read ?? false
|
||||
let title = read ? NSLocalizedString("Mark as Unread", comment: "Mark as Unread") : NSLocalizedString("Mark as Read", comment: "Mark as Read")
|
||||
let readImage = read ? AppAssets.circleClosedImage : AppAssets.circleOpenImage
|
||||
return UIAction(title: title, image: readImage) { [weak self] action in
|
||||
self?.coordinator.toggleReadForCurrentArticle()
|
||||
}
|
||||
}
|
||||
|
||||
func toggleStarredAction() -> UIAction {
|
||||
let starred = currentArticle?.status.starred ?? false
|
||||
let title = starred ? NSLocalizedString("Mark as Unstarred", comment: "Mark as Unstarred") : NSLocalizedString("Mark as Starred", comment: "Mark as Starred")
|
||||
let starredImage = starred ? AppAssets.starOpenImage : AppAssets.starClosedImage
|
||||
return UIAction(title: title, image: starredImage) { [weak self] action in
|
||||
self?.coordinator.toggleStarredForCurrentArticle()
|
||||
}
|
||||
}
|
||||
|
||||
func nextUnreadArticleAction() -> UIAction? {
|
||||
guard coordinator.isAnyUnreadAvailable else { return nil }
|
||||
let title = NSLocalizedString("Next Unread Article", comment: "Next Unread Article")
|
||||
return UIAction(title: title, image: AppAssets.nextUnreadArticleImage) { [weak self] action in
|
||||
self?.coordinator.selectNextArticle()
|
||||
}
|
||||
}
|
||||
|
||||
func toggleArticleExtractorAction() -> UIAction {
|
||||
let extracted = articleExtractorButton.buttonState == .on
|
||||
let title = extracted ? NSLocalizedString("Show Feed Article", comment: "Show Feed Article") : NSLocalizedString("Show Reader View", comment: "Show Reader View")
|
||||
let extractorImage = extracted ? AppAssets.articleExtractorOffSmall : AppAssets.articleExtractorOnSmall
|
||||
return UIAction(title: title, image: extractorImage) { [weak self] action in
|
||||
self?.coordinator.toggleArticleExtractor()
|
||||
}
|
||||
}
|
||||
|
||||
func shareAction() -> UIAction {
|
||||
let title = NSLocalizedString("Share", comment: "Share")
|
||||
return UIAction(title: title, image: AppAssets.shareImage) { [weak self] action in
|
||||
self?.showActivityDialog()
|
||||
}
|
||||
}
|
||||
|
||||
|
50
iOS/Article/ContextMenuPreviewViewController.swift
Normal file
50
iOS/Article/ContextMenuPreviewViewController.swift
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// ContextMenuPreviewViewController.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 11/25/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Articles
|
||||
|
||||
class ContextMenuPreviewViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var blogNameLabel: UILabel!
|
||||
@IBOutlet weak var blogAuthorLabel: UILabel!
|
||||
@IBOutlet weak var articleTitleLabel: UILabel!
|
||||
@IBOutlet weak var dateTimeLabel: UILabel!
|
||||
|
||||
var article: Article!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
blogNameLabel.text = article.webFeed?.nameForDisplay ?? ""
|
||||
blogAuthorLabel.text = article.byline()
|
||||
articleTitleLabel.text = article.title ?? ""
|
||||
|
||||
let icon = IconView()
|
||||
icon.iconImage = article.iconImage()
|
||||
icon.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(icon)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
icon.widthAnchor.constraint(equalToConstant: 48),
|
||||
icon.heightAnchor.constraint(equalToConstant: 48),
|
||||
icon.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8),
|
||||
icon.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20)
|
||||
])
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .long
|
||||
dateFormatter.timeStyle = .medium
|
||||
dateTimeLabel.text = dateFormatter.string(from: article.logicalDatePublished)
|
||||
|
||||
view.setNeedsLayout()
|
||||
view.layoutIfNeeded()
|
||||
preferredContentSize = CGSize(width: view.bounds.width, height: dateTimeLabel.frame.maxY + 8)
|
||||
}
|
||||
|
||||
}
|
@ -299,6 +299,93 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3056.521739130435" y="-759.375"/>
|
||||
</scene>
|
||||
<!--Context Menu Preview View Controller-->
|
||||
<scene sceneID="Tc4-Ma-XSa">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ContextMenuPreviewViewController" id="CoM-D3-PNS" customClass="ContextMenuPreviewViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="eH6-Fa-Tfi">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="200"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Blog Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YsT-Lt-Zry">
|
||||
<rect key="frame" x="20" y="8" width="87" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<color key="textColor" name="primaryAccentColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Blog Author" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7GV-PV-YVq">
|
||||
<rect key="frame" x="20" y="36.5" width="91" height="21"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Article Title" textAlignment="natural" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iFp-rn-HhQ">
|
||||
<rect key="frame" x="20" y="74.5" width="136" height="33.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0Hz-Dv-MhU">
|
||||
<rect key="frame" x="20" y="116" width="44" height="20.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9Ms-dt-2M8">
|
||||
<rect key="frame" x="346" y="8" width="48" height="48"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="48" id="d19-Jv-DFz"/>
|
||||
<constraint firstAttribute="height" constant="48" id="vvL-LM-Qkp"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0ko-zB-cnS">
|
||||
<rect key="frame" x="20" y="65.5" width="374" height="1"/>
|
||||
<color key="backgroundColor" systemColor="separatorColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.28999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="1" id="IVk-Gd-niT"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="9Ms-dt-2M8" firstAttribute="top" secondItem="eH6-Fa-Tfi" secondAttribute="top" constant="8" id="ECM-0Y-axL"/>
|
||||
<constraint firstItem="0Hz-Dv-MhU" firstAttribute="leading" secondItem="d1t-hb-otl" secondAttribute="leading" constant="20" id="GCs-jq-FwF"/>
|
||||
<constraint firstItem="iFp-rn-HhQ" firstAttribute="top" secondItem="0ko-zB-cnS" secondAttribute="bottom" constant="8" id="HCu-Fi-dC8"/>
|
||||
<constraint firstItem="7GV-PV-YVq" firstAttribute="top" secondItem="YsT-Lt-Zry" secondAttribute="bottom" constant="8" id="HCw-VQ-FWp"/>
|
||||
<constraint firstItem="YsT-Lt-Zry" firstAttribute="top" secondItem="eH6-Fa-Tfi" secondAttribute="top" constant="8" id="IbT-5V-iPB"/>
|
||||
<constraint firstItem="iFp-rn-HhQ" firstAttribute="leading" secondItem="d1t-hb-otl" secondAttribute="leading" constant="20" id="MyB-pX-SCv"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="iFp-rn-HhQ" secondAttribute="trailing" constant="20" id="NF0-QV-MJa"/>
|
||||
<constraint firstItem="7GV-PV-YVq" firstAttribute="leading" secondItem="d1t-hb-otl" secondAttribute="leading" constant="20" id="Rh6-Ug-Rkf"/>
|
||||
<constraint firstAttribute="trailing" secondItem="0ko-zB-cnS" secondAttribute="trailing" constant="20" id="Sfv-FQ-fXh"/>
|
||||
<constraint firstItem="0Hz-Dv-MhU" firstAttribute="top" secondItem="iFp-rn-HhQ" secondAttribute="bottom" constant="8" id="b1a-tF-MdY"/>
|
||||
<constraint firstItem="YsT-Lt-Zry" firstAttribute="leading" secondItem="d1t-hb-otl" secondAttribute="leading" constant="20" id="fXj-St-fed"/>
|
||||
<constraint firstItem="9Ms-dt-2M8" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="7GV-PV-YVq" secondAttribute="trailing" constant="8" id="hkE-jR-WyS"/>
|
||||
<constraint firstItem="0ko-zB-cnS" firstAttribute="top" relation="greaterThanOrEqual" secondItem="9Ms-dt-2M8" secondAttribute="bottom" constant="8" id="kvc-Go-qdz"/>
|
||||
<constraint firstItem="d1t-hb-otl" firstAttribute="trailing" secondItem="9Ms-dt-2M8" secondAttribute="trailing" constant="20" id="mO6-1A-xSW"/>
|
||||
<constraint firstItem="9Ms-dt-2M8" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="YsT-Lt-Zry" secondAttribute="trailing" constant="8" id="pAW-iQ-2lB"/>
|
||||
<constraint firstItem="0ko-zB-cnS" firstAttribute="top" secondItem="7GV-PV-YVq" secondAttribute="bottom" constant="8" id="rVh-Lq-DrY"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="0Hz-Dv-MhU" secondAttribute="trailing" constant="20" id="sg6-sh-fl5"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="0Hz-Dv-MhU" secondAttribute="bottom" constant="8" id="usR-Xq-BeL"/>
|
||||
<constraint firstItem="0ko-zB-cnS" firstAttribute="leading" secondItem="eH6-Fa-Tfi" secondAttribute="leading" constant="20" id="wPH-RZ-ZJq"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="d1t-hb-otl"/>
|
||||
</view>
|
||||
<nil key="simulatedTopBarMetrics"/>
|
||||
<nil key="simulatedBottomBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="414" height="200"/>
|
||||
<connections>
|
||||
<outlet property="articleTitleLabel" destination="iFp-rn-HhQ" id="nxQ-GW-QP3"/>
|
||||
<outlet property="blogAuthorLabel" destination="7GV-PV-YVq" id="xoY-pG-H7S"/>
|
||||
<outlet property="blogNameLabel" destination="YsT-Lt-Zry" id="WVx-Mh-Fn7"/>
|
||||
<outlet property="dateTimeLabel" destination="0Hz-Dv-MhU" id="QaU-do-WRo"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="vGQ-wP-i7Q" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3763.7681159420295" y="-983.03571428571422"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="chevron.down" catalog="system" width="64" height="36"/>
|
||||
|
@ -10,6 +10,7 @@
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
"template-rendering-intent" : "template",
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"template-rendering-intent" : "template"
|
||||
"template-rendering-intent" : "template",
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user