From fe3fa220bb426b47bacb946d3637b894d92af1b9 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 17 Sep 2019 17:00:23 -0500 Subject: [PATCH] Port TimelineAvatarView to iOS --- NetNewsWire.xcodeproj/project.pbxproj | 24 +++-- .../Cell/MasterTimelineAvatarView.swift | 101 ++++++++++++++++++ .../Cell/MasterTimelineTableViewCell.swift | 28 +++-- 3 files changed, 127 insertions(+), 26 deletions(-) create mode 100644 iOS/MasterTimeline/Cell/MasterTimelineAvatarView.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 745af6010..922cfbdd6 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -99,6 +99,7 @@ 51AF460323247321001742EF /* SettingsDetailAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F772EC22B2789B0087D9D1 /* SettingsDetailAccountView.swift */; }; 51AF460C23247F11001742EF /* SettingsFeedbinAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510D707F22B02A5F004E8F65 /* SettingsFeedbinAccountView.swift */; }; 51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51AF460D232488C6001742EF /* Account-Extensions.swift */; }; + 51B62E68233186730085F949 /* MasterTimelineAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B62E67233186730085F949 /* MasterTimelineAvatarView.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 */; }; @@ -813,6 +814,7 @@ 519D740523243CC0008BB345 /* RefreshInterval-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RefreshInterval-Extensions.swift"; sourceTree = ""; }; 519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 51AF460D232488C6001742EF /* Account-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account-Extensions.swift"; sourceTree = ""; }; + 51B62E67233186730085F949 /* MasterTimelineAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineAvatarView.swift; sourceTree = ""; }; 51C4524E226506F400C03939 /* UIStoryboard-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIStoryboard-Extensions.swift"; sourceTree = ""; }; 51C45250226506F400C03939 /* String-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String-Extensions.swift"; sourceTree = ""; }; 51C45254226507D200C03939 /* AppAssets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAssets.swift; sourceTree = ""; }; @@ -1036,9 +1038,9 @@ 84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Vendor/Sparkle.framework; sourceTree = SOURCE_ROOT; }; 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = ""; }; - D519E74722EE553300923F27 /* NetNewsWire_safariextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_safariextension_target.xcconfig; sourceTree = ""; }; B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = ""; }; B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = ""; }; + D519E74722EE553300923F27 /* NetNewsWire_safariextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_safariextension_target.xcconfig; sourceTree = ""; }; D553737C20186C1F006D8857 /* Article+Scriptability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Article+Scriptability.swift"; sourceTree = ""; }; D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScriptCommand+NetNewsWire.swift"; sourceTree = ""; }; D5907CDC2002F0BE005947E5 /* NetNewsWire_project_release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_project_release.xcconfig; sourceTree = ""; }; @@ -1326,14 +1328,15 @@ 51C4526F2265091600C03939 /* Cell */ = { isa = PBXGroup; children = ( - 51C452722265091600C03939 /* MasterTimelineTableViewCell.swift */, + 51EF0F7D2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift */, + 51B62E67233186730085F949 /* MasterTimelineAvatarView.swift */, 51C452712265091600C03939 /* MasterTimelineCellData.swift */, 51EF0F7F2277A8330050506E /* MasterTimelineCellLayout.swift */, 51C452752265091600C03939 /* MasterTimelineDefaultCellLayout.swift */, - 51EF0F7D2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift */, + 51C452722265091600C03939 /* MasterTimelineTableViewCell.swift */, 51C452742265091600C03939 /* MasterUnreadIndicatorView.swift */, - 51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */, 51C452702265091600C03939 /* MultilineUILabelSizer.swift */, + 51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */, ); path = Cell; sourceTree = ""; @@ -2195,12 +2198,12 @@ ProvisioningStyle = Automatic; }; 6581C73220CED60000F4AD34 = { - DevelopmentTeam = M8L2WTLA8W; - ProvisioningStyle = Manual; + DevelopmentTeam = SHJK2V3AJG; + ProvisioningStyle = Automatic; }; 840D617B2029031C009BC708 = { CreatedOnToolsVersion = 9.3; - DevelopmentTeam = M8L2WTLA8W; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.BackgroundModes = { @@ -2210,8 +2213,8 @@ }; 849C645F1ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = M8L2WTLA8W; - ProvisioningStyle = Manual; + DevelopmentTeam = SHJK2V3AJG; + ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.HardenedRuntime = { enabled = 1; @@ -2220,7 +2223,7 @@ }; 849C64701ED37A5D003D8FC0 = { CreatedOnToolsVersion = 8.2.1; - DevelopmentTeam = 9C84TZ7Q6Z; + DevelopmentTeam = SHJK2V3AJG; ProvisioningStyle = Automatic; TestTargetID = 849C645F1ED37A5D003D8FC0; }; @@ -2625,6 +2628,7 @@ 5152E1022324900D00E5C7AD /* SettingsAddAccountView.swift in Sources */, 515ADE4022E11FAE006B2460 /* SystemMessageViewController.swift in Sources */, 51F85BF92274AA7B00C787DC /* UIBarButtonItem-Extensions.swift in Sources */, + 51B62E68233186730085F949 /* MasterTimelineAvatarView.swift in Sources */, 51C45296226509D300C03939 /* OPMLExporter.swift in Sources */, 51C45291226509C800C03939 /* SmartFeed.swift in Sources */, 51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */, diff --git a/iOS/MasterTimeline/Cell/MasterTimelineAvatarView.swift b/iOS/MasterTimeline/Cell/MasterTimelineAvatarView.swift new file mode 100644 index 000000000..8973c71bb --- /dev/null +++ b/iOS/MasterTimeline/Cell/MasterTimelineAvatarView.swift @@ -0,0 +1,101 @@ +// +// MasterTimelineAvatarView.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 9/17/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit + +final class MasterTimelineAvatarView: UIView { + + var image: UIImage? = nil { + didSet { + if image !== oldValue { + imageView.image = image + setNeedsLayout() + setNeedsDisplay() + } + } + } + + private let imageView: UIImageView = { + let imageView = NonIntrinsicImageView(image: AppAssets.feedImage) + imageView.contentMode = .scaleAspectFit + return imageView + }() + + private var hasExposedVerticalBackground: Bool { + return imageView.frame.size.height < bounds.size.height + } + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + convenience init() { + self.init(frame: .zero) + } + + override func didMoveToSuperview() { + setNeedsLayout() + setNeedsDisplay() + } + + override func layoutSubviews() { + imageView.setFrameIfNotEqual(rectForImageView()) + } + + override func draw(_ dirtyRect: CGRect) { + if hasExposedVerticalBackground { + AppAssets.avatarBackgroundColor.set() + } else { + UIColor.clear.set() + } + UIRectFill(dirtyRect) + } +} + +private extension MasterTimelineAvatarView { + + func commonInit() { + addSubview(imageView) + } + + func rectForImageView() -> CGRect { + guard let image = image else { + return CGRect.zero + } + + let imageSize = image.size + let viewSize = bounds.size + if imageSize.height == imageSize.width { + if imageSize.height >= viewSize.height * 0.75 { + // Close enough to viewSize to scale up the image. + return CGRect(x: 0.0, y: 0.0, width: viewSize.width, height: viewSize.height) + } + let offset = floor((viewSize.height - imageSize.height) / 2.0) + return CGRect(x: offset, y: offset, width: imageSize.width, height: imageSize.height) + } + else if imageSize.height > imageSize.width { + let factor = viewSize.height / imageSize.height + let width = imageSize.width * factor + let originX = floor((viewSize.width - width) / 2.0) + return CGRect(x: originX, y: 0.0, width: width, height: viewSize.height) + } + + // Wider than tall: imageSize.width > imageSize.height + let factor = viewSize.width / imageSize.width + let height = imageSize.height * factor + let originY = floor((viewSize.height - height) / 2.0) + return CGRect(x: 0.0, y: originY, width: viewSize.width, height: height) + } + +} diff --git a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift index 1fc979a8d..a9b2d250c 100644 --- a/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift +++ b/iOS/MasterTimeline/Cell/MasterTimelineTableViewCell.swift @@ -17,11 +17,7 @@ class MasterTimelineTableViewCell: NNWTableViewCell { private let dateView = MasterTimelineTableViewCell.singleLineUILabel() private let feedNameView = MasterTimelineTableViewCell.singleLineUILabel() - private lazy var avatarImageView: UIImageView = { - let imageView = NonIntrinsicImageView(image: AppAssets.feedImage) - imageView.contentMode = .scaleAspectFit - return imageView - }() + private lazy var avatarView = MasterTimelineAvatarView() private lazy var starView = { return NonIntrinsicImageView(image: AppAssets.timelineStarImage) @@ -78,7 +74,7 @@ class MasterTimelineTableViewCell: NNWTableViewCell { unreadIndicatorView.setFrameIfNotEqual(layout.unreadIndicatorRect) starView.setFrameIfNotEqual(layout.starRect) - avatarImageView.setFrameIfNotEqual(layout.avatarImageRect) + avatarView.setFrameIfNotEqual(layout.avatarImageRect) setFrame(for: titleView, rect: layout.titleRect) setFrame(for: summaryView, rect: layout.summaryRect) feedNameView.setFrameIfNotEqual(layout.feedNameRect) @@ -89,7 +85,7 @@ class MasterTimelineTableViewCell: NNWTableViewCell { } func setAvatarImage(_ image: UIImage) { - avatarImageView.image = image + avatarView.image = image } } @@ -140,7 +136,7 @@ private extension MasterTimelineTableViewCell { addSubviewAtInit(unreadIndicatorView, hidden: true) addSubviewAtInit(dateView, hidden: false) addSubviewAtInit(feedNameView, hidden: true) - addSubviewAtInit(avatarImageView, hidden: true) + addSubviewAtInit(avatarView, hidden: true) addSubviewAtInit(starView, hidden: true) } @@ -211,23 +207,23 @@ private extension MasterTimelineTableViewCell { return } - showView(avatarImageView) - avatarImageView.layer.cornerRadius = MasterTimelineDefaultCellLayout.avatarCornerRadius - avatarImageView.clipsToBounds = true + showView(avatarView) + avatarView.layer.cornerRadius = MasterTimelineDefaultCellLayout.avatarCornerRadius + avatarView.clipsToBounds = true - if avatarImageView.image !== cellData.avatar { - avatarImageView.image = image + if avatarView.image !== cellData.avatar { + avatarView.image = image setNeedsLayout() } } func makeAvatarEmpty() { - if avatarImageView.image != nil { - avatarImageView.image = nil + if avatarView.image != nil { + avatarView.image = nil setNeedsLayout() } - hideView(avatarImageView) + hideView(avatarView) } func hideView(_ view: UIView) {