From 710d164186a0ff211af5dab267caa07ea92a231f Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 23 Feb 2018 12:33:39 -0500 Subject: [PATCH 1/4] Update minimum iOS version to 9.0. --- Podfile | 2 +- Podfile.lock | 4 ++-- Pods | 2 +- Signal.xcodeproj/project.pbxproj | 12 ++++++------ SignalServiceKit.podspec | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Podfile b/Podfile index 706f582ab..484568243 100644 --- a/Podfile +++ b/Podfile @@ -1,4 +1,4 @@ -platform :ios, '8.0' +platform :ios, '9.0' source 'https://github.com/CocoaPods/Specs.git' use_frameworks! diff --git a/Podfile.lock b/Podfile.lock index d72cbbb1f..793848dc7 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -211,7 +211,7 @@ SPEC CHECKSUMS: PureLayout: 4d550abe49a94f24c2808b9b95db9131685fe4cd Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c - SignalServiceKit: 2dbddc0638e387feb714d9cc68962d4956c8ebe5 + SignalServiceKit: c639443811f2670986006f5b661cb1a862de66ab SocketRocket: dbb1554b8fc288ef8ef370d6285aeca7361be31e SQLCipher: f9fcf29b2e59ced7defc2a2bdd0ebe79b40d4990 SSZipArchive: 14401ade5f8e82aba1ff03e9f88e9de60937ae60 @@ -220,6 +220,6 @@ SPEC CHECKSUMS: YapDatabase: 299a32de9d350d37a9ac5b0532609d87d5d2a5de YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54 -PODFILE CHECKSUM: 59053e1c79fca64b3429210d11db5520977f49cb +PODFILE CHECKSUM: 66db99df53e7593362ebb004bea5d2215ca00e8e COCOAPODS: 1.3.1 diff --git a/Pods b/Pods index dab63d541..29babe215 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit dab63d5412cf0d8777bd11259c86a56b1ec25bab +Subproject commit 29babe215072d52688ea5b18592f308bfc190612 diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 3817c5a80..6bb21b2f0 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -3366,7 +3366,7 @@ GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ""; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-fobjc-arc-exceptions"; SDKROOT = iphoneos; @@ -3413,7 +3413,7 @@ "\"$(SRCROOT)/Libraries\"/**", ); INFOPLIST_FILE = "$(SRCROOT)/Signal/Signal-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -3954,7 +3954,7 @@ GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ""; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-fobjc-arc-exceptions"; SDKROOT = iphoneos; @@ -4018,7 +4018,7 @@ GCC_WARN_UNUSED_VALUE = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = ""; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "-DNS_BLOCK_ASSERTIONS=1", @@ -4069,7 +4069,7 @@ "\"$(SRCROOT)/Libraries\"/**", ); INFOPLIST_FILE = "$(SRCROOT)/Signal/Signal-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -4130,7 +4130,7 @@ "\"$(SRCROOT)/Libraries\"/**", ); INFOPLIST_FILE = "$(SRCROOT)/Signal/Signal-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", diff --git a/SignalServiceKit.podspec b/SignalServiceKit.podspec index 293fac037..0d67ba3c2 100644 --- a/SignalServiceKit.podspec +++ b/SignalServiceKit.podspec @@ -21,8 +21,8 @@ An Objective-C library for communicating with the Signal messaging service. s.source = { :git => "https://github.com/signalapp/SignalServiceKit.git", :tag => s.version.to_s } s.social_media_url = 'https://twitter.com/FredericJacobs' - s.platform = :ios, '8.0' - #s.ios.deployment_target = '8.0' + s.platform = :ios, '9.0' + #s.ios.deployment_target = '9.0' #s.osx.deployment_target = '10.9' s.requires_arc = true s.source_files = 'SignalServiceKit/src/**/*.{h,m,mm}' From 44e38709d612adf896f057805cb1f50ee564bfd4 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 23 Feb 2018 12:34:39 -0500 Subject: [PATCH 2/4] Update minimum iOS version to 9.0. --- Signal.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 6bb21b2f0..e31a2969b 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -3722,7 +3722,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SignalMessaging/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = org.whispersystems.signal.SignalMessaging; @@ -3799,7 +3799,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = SignalMessaging/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = org.whispersystems.signal.SignalMessaging; @@ -3876,7 +3876,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = SignalMessaging/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = org.whispersystems.signal.SignalMessaging; From 99aedca45f46b33579e941a7791e89668a294f3f Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 23 Feb 2018 12:46:04 -0500 Subject: [PATCH 3/4] Strip out special casing for pre-iOS 9 users. --- .../src/ViewControllers/ContactsPicker.swift | 6 - .../ConversationInputTextView.m | 14 +- ...ExperienceUpgradesPageViewController.swift | 26 +- Signal/src/ViewControllers/InviteFlow.swift | 37 +- .../MediaDetailViewController.m | 95 ++- Signal/src/call/CallAudioService.swift | 8 +- Signal/src/network/PushManager.m | 8 +- Signal/src/views/ContactCell.swift | 12 +- Signal/src/views/MarqueeLabel.swift | 546 +++++++++--------- Signal/src/views/RemoteVideoView.m | 11 +- Signal/test/contact/ContactsPickerTest.swift | 4 +- .../AttachmentApprovalViewController.swift | 91 +-- .../attachments/OWSVideoPlayer.swift | 8 +- .../attachments/VideoPlayerView.swift | 5 +- SignalMessaging/categories/UIFont+OWS.m | 41 +- SignalMessaging/categories/UIView+OWS.m | 104 +--- .../contacts/SystemContactsFetcher.swift | 178 +----- SignalMessaging/views/OWSAlerts.swift | 14 +- 18 files changed, 419 insertions(+), 789 deletions(-) diff --git a/Signal/src/ViewControllers/ContactsPicker.swift b/Signal/src/ViewControllers/ContactsPicker.swift index 2236b6242..65f9fef59 100644 --- a/Signal/src/ViewControllers/ContactsPicker.swift +++ b/Signal/src/ViewControllers/ContactsPicker.swift @@ -11,7 +11,6 @@ import UIKit import Contacts import SignalServiceKit -@available(iOS 9.0, *) public protocol ContactsPickerDelegate { func contactsPicker(_: ContactsPicker, didContactFetchFailed error: NSError) func contactsPicker(_: ContactsPicker, didCancel error: NSError) @@ -20,7 +19,6 @@ public protocol ContactsPickerDelegate { func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool } -@available(iOS 9.0, *) public extension ContactsPickerDelegate { func contactsPicker(_: ContactsPicker, didContactFetchFailed error: NSError) { } func contactsPicker(_: ContactsPicker, didCancel error: NSError) { } @@ -34,7 +32,6 @@ public enum SubtitleCellValue { case email } -@available(iOS 9.0, *) open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate { @IBOutlet var tableView: UITableView! @@ -336,10 +333,8 @@ open class ContactsPicker: OWSViewController, UITableViewDelegate, UITableViewDa } } -@available(iOS 9.0, *) let ContactSortOrder = computeSortOrder() -@available(iOS 9.0, *) func computeSortOrder() -> CNContactSortOrder { let comparator = CNContact.comparator(forNameSortOrder: .userDefault) @@ -360,7 +355,6 @@ func computeSortOrder() -> CNContactSortOrder { } } -@available(iOS 9.0, *) fileprivate extension CNContact { /** * Sorting Key used by collation diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.m b/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.m index e7e793677..cc2c4a8c8 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputTextView.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "ConversationInputTextView.h" @@ -215,14 +215,10 @@ NS_ASSUME_NONNULL_BEGIN action:(SEL)action discoverabilityTitle:(NSString *)discoverabilityTitle { - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(9, 0)) { - return [UIKeyCommand keyCommandWithInput:input - modifierFlags:modifierFlags - action:action - discoverabilityTitle:discoverabilityTitle]; - } else { - return [UIKeyCommand keyCommandWithInput:input modifierFlags:modifierFlags action:action]; - } + return [UIKeyCommand keyCommandWithInput:input + modifierFlags:modifierFlags + action:action + discoverabilityTitle:discoverabilityTitle]; } - (void)modifiedReturnPressed:(UIKeyCommand *)sender diff --git a/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift b/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift index 75f92d838..7772c46f9 100644 --- a/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift +++ b/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift @@ -51,7 +51,7 @@ private class IntroductingReadReceiptsExperienceUpgradeViewController: Experienc // Construct the "settings" view & push the "privacy settings" view. let navigationController = AppSettingsViewController.inModalNavigationController() - navigationController.pushViewController(PrivacySettingsTableViewController(), animated:false) + navigationController.pushViewController(PrivacySettingsTableViewController(), animated: false) fromViewController.present(navigationController, animated: true) } @@ -96,7 +96,7 @@ private class IntroductingReadReceiptsExperienceUpgradeViewController: Experienc button.setTitle(title, for: .normal) button.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal) button.isUserInteractionEnabled = true - button.addTarget(self, action:#selector(didTapButton), for: .touchUpInside) + button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside) button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) button.titleLabel?.textAlignment = .center button.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(18)) @@ -201,7 +201,7 @@ private class IntroductingProfilesExperienceUpgradeViewController: ExperienceUpg button.backgroundColor = UIColor.ows_materialBlue button.isUserInteractionEnabled = true - button.addTarget(self, action:#selector(didTapButton), for: .touchUpInside) + button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside) button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20) button.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(18)) @@ -261,7 +261,7 @@ private class CallKitExperienceUpgradeViewController: ExperienceUpgradeViewContr privacySettingsButton.setTitle(privacyTitle, for: .normal) privacySettingsButton.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal) privacySettingsButton.isUserInteractionEnabled = true - privacySettingsButton.addTarget(self, action:#selector(didTapPrivacySettingsButton), for: .touchUpInside) + privacySettingsButton.addTarget(self, action: #selector(didTapPrivacySettingsButton), for: .touchUpInside) privacySettingsButton.titleLabel?.font = bodyLabel.font // Privacy Settings Button layout @@ -282,7 +282,7 @@ private class CallKitExperienceUpgradeViewController: ExperienceUpgradeViewContr // Construct the "settings" view & push the "privacy settings" view. let navigationController = AppSettingsViewController.inModalNavigationController() - navigationController.pushViewController(PrivacySettingsTableViewController(), animated:false) + navigationController.pushViewController(PrivacySettingsTableViewController(), animated: false) fromViewController?.present(navigationController, animated: true, completion: nil) } @@ -365,13 +365,9 @@ private class ExperienceUpgradeViewController: OWSViewController { } func setPageControlAppearance() { - if #available(iOS 9.0, *) { - let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [UIPageViewController.self]) - pageControl.pageIndicatorTintColor = UIColor.lightGray - pageControl.currentPageIndicatorTintColor = UIColor.ows_materialBlue - } else { - // iOS8 won't see the page controls =( - } + let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [UIPageViewController.self]) + pageControl.pageIndicatorTintColor = UIColor.lightGray + pageControl.currentPageIndicatorTintColor = UIColor.ows_materialBlue } class ExperienceUpgradesPageViewController: OWSViewController, UIPageViewControllerDataSource { @@ -390,7 +386,7 @@ class ExperienceUpgradesPageViewController: OWSViewController, UIPageViewControl self.experienceUpgrades = experienceUpgrades setPageControlAppearance() - self.pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation:.horizontal, options: nil) + self.pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) super.init(nibName: nil, bundle: nil) self.pageViewController.dataSource = self @@ -402,7 +398,7 @@ class ExperienceUpgradesPageViewController: OWSViewController, UIPageViewControl assert(false) // This should never happen, but so as not to explode we give some bogus data self.experienceUpgrades = [ExperienceUpgrade()] - self.pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation:.horizontal, options: nil) + self.pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) super.init(coder: aDecoder) self.pageViewController.dataSource = self } @@ -441,7 +437,7 @@ class ExperienceUpgradesPageViewController: OWSViewController, UIPageViewControl dismissButton.setTitle(CommonStrings.dismissButton, for: .normal) dismissButton.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal) dismissButton.isUserInteractionEnabled = true - dismissButton.addTarget(self, action:#selector(didTapDismissButton), for: .touchUpInside) + dismissButton.addTarget(self, action: #selector(didTapDismissButton), for: .touchUpInside) dismissButton.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(16)) let dismissInsetValue: CGFloat = ScaleFromIPhone5(10) dismissButton.contentEdgeInsets = UIEdgeInsets(top: dismissInsetValue, left: dismissInsetValue, bottom: dismissInsetValue, right: dismissInsetValue) diff --git a/Signal/src/ViewControllers/InviteFlow.swift b/Signal/src/ViewControllers/InviteFlow.swift index f8cc8d821..7380d13fd 100644 --- a/Signal/src/ViewControllers/InviteFlow.swift +++ b/Signal/src/ViewControllers/InviteFlow.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // import Foundation @@ -34,14 +34,12 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos actionSheetController.addAction(dismissAction()) - if #available(iOS 9.0, *) { - if let messageAction = messageAction() { - actionSheetController.addAction(messageAction) - } + if let messageAction = messageAction() { + actionSheetController.addAction(messageAction) + } - if let mailAction = mailAction() { - actionSheetController.addAction(mailAction) - } + if let mailAction = mailAction() { + actionSheetController.addAction(mailAction) } if let tweetAction = tweetAction() { @@ -66,14 +64,14 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos return nil } - let tweetString = NSLocalizedString("SETTINGS_INVITE_TWITTER_TEXT", comment:"content of tweet when inviting via twitter") + let tweetString = NSLocalizedString("SETTINGS_INVITE_TWITTER_TEXT", comment: "content of tweet when inviting via twitter") twitterViewController.setInitialText(tweetString) let tweetUrl = URL(string: installUrl) twitterViewController.add(tweetUrl) twitterViewController.add(#imageLiteral(resourceName: "twitter_sharing_image")) - let tweetTitle = NSLocalizedString("SHARE_ACTION_TWEET", comment:"action sheet item") + let tweetTitle = NSLocalizedString("SHARE_ACTION_TWEET", comment: "action sheet item") return UIAlertAction(title: tweetTitle, style: .default) { _ in Logger.debug("\(self.TAG) Chose tweet") @@ -87,7 +85,6 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos // MARK: ContactsPickerDelegate - @available(iOS 9.0, *) func contactsPicker(_: ContactsPicker, didSelectMultipleContacts contacts: [Contact]) { Logger.debug("\(TAG) didSelectContacts:\(contacts)") @@ -108,7 +105,6 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos } } - @available(iOS 9.0, *) func contactsPicker(_: ContactsPicker, shouldSelectContact contact: Contact) -> Bool { guard let inviteChannel = channel else { Logger.error("\(TAG) unexpected nil channel in contact picker.") @@ -128,7 +124,6 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos // MARK: SMS - @available(iOS 9.0, *) func messageAction() -> UIAlertAction? { guard MFMessageComposeViewController.canSendText() else { Logger.info("\(TAG) Device cannot send text") @@ -164,9 +159,9 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos messageComposeViewController.messageComposeDelegate = self messageComposeViewController.recipients = phoneNumbers - let inviteText = NSLocalizedString("SMS_INVITE_BODY", comment:"body sent to contacts when inviting to Install Signal") + let inviteText = NSLocalizedString("SMS_INVITE_BODY", comment: "body sent to contacts when inviting to Install Signal") messageComposeViewController.body = inviteText.appending(" \(self.installUrl)") - self.presentingViewController.navigationController?.present(messageComposeViewController, animated:true) + self.presentingViewController.navigationController?.present(messageComposeViewController, animated: true) } // MARK: MessageComposeViewControllerDelegate @@ -178,7 +173,7 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos switch result { case .failed: - let warning = UIAlertController(title: nil, message: NSLocalizedString("SEND_INVITE_FAILURE", comment:"Alert body after invite failed"), preferredStyle: .alert) + let warning = UIAlertController(title: nil, message: NSLocalizedString("SEND_INVITE_FAILURE", comment: "Alert body after invite failed"), preferredStyle: .alert) warning.addAction(UIAlertAction(title: CommonStrings.dismissButton, style: .default, handler: nil)) self.presentingViewController.present(warning, animated: true, completion: nil) case .sent: @@ -190,7 +185,6 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos // MARK: Mail - @available(iOS 9.0, *) func mailAction() -> UIAlertAction? { guard MFMailComposeViewController.canSendMail() else { Logger.info("\(TAG) Device cannot send mail") @@ -208,21 +202,20 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos } } - @available(iOS 9.0, *) func sendMailTo(emails recipientEmails: [String]) { let mailComposeViewController = MFMailComposeViewController() mailComposeViewController.mailComposeDelegate = self mailComposeViewController.setBccRecipients(recipientEmails) - let subject = NSLocalizedString("EMAIL_INVITE_SUBJECT", comment:"subject of email sent to contacts when inviting to install Signal") - let bodyFormat = NSLocalizedString("EMAIL_INVITE_BODY", comment:"body of email sent to contacts when inviting to install Signal. Embeds {{link to install Signal}} and {{link to WhisperSystems home page}}") + let subject = NSLocalizedString("EMAIL_INVITE_SUBJECT", comment: "subject of email sent to contacts when inviting to install Signal") + let bodyFormat = NSLocalizedString("EMAIL_INVITE_BODY", comment: "body of email sent to contacts when inviting to install Signal. Embeds {{link to install Signal}} and {{link to WhisperSystems home page}}") let body = String.init(format: bodyFormat, installUrl, homepageUrl) mailComposeViewController.setSubject(subject) mailComposeViewController.setMessageBody(body, isHTML: false) self.presentingViewController.dismiss(animated: true) { - self.presentingViewController.navigationController?.present(mailComposeViewController, animated:true) { + self.presentingViewController.navigationController?.present(mailComposeViewController, animated: true) { UIUtil.applySignalAppearence() } } @@ -235,7 +228,7 @@ class InviteFlow: NSObject, MFMessageComposeViewControllerDelegate, MFMailCompos switch result { case .failed: - let warning = UIAlertController(title: nil, message: NSLocalizedString("SEND_INVITE_FAILURE", comment:"Alert body after invite failed"), preferredStyle: .alert) + let warning = UIAlertController(title: nil, message: NSLocalizedString("SEND_INVITE_FAILURE", comment: "Alert body after invite failed"), preferredStyle: .alert) warning.addAction(UIAlertAction(title: CommonStrings.dismissButton, style: .default, handler: nil)) self.presentingViewController.present(warning, animated: true, completion: nil) case .sent: diff --git a/Signal/src/ViewControllers/MediaDetailViewController.m b/Signal/src/ViewControllers/MediaDetailViewController.m index c84e9d1da..23b8f6d37 100644 --- a/Signal/src/ViewControllers/MediaDetailViewController.m +++ b/Signal/src/ViewControllers/MediaDetailViewController.m @@ -312,18 +312,16 @@ NS_ASSUME_NONNULL_BEGIN [self applyInitialMediaViewConstraints]; if (self.isVideo) { - if (@available(iOS 9, *)) { - PlayerProgressBar *videoProgressBar = [PlayerProgressBar new]; - videoProgressBar.delegate = self; - videoProgressBar.player = self.videoPlayer.avPlayer; - - self.videoProgressBar = videoProgressBar; - [self.view addSubview:videoProgressBar]; - [videoProgressBar autoPinWidthToSuperview]; - [videoProgressBar autoPinToTopLayoutGuideOfViewController:self withInset:0]; - CGFloat kVideoProgressBarHeight = 44; - [videoProgressBar autoSetDimension:ALDimensionHeight toSize:kVideoProgressBarHeight]; - } + PlayerProgressBar *videoProgressBar = [PlayerProgressBar new]; + videoProgressBar.delegate = self; + videoProgressBar.player = self.videoPlayer.avPlayer; + + self.videoProgressBar = videoProgressBar; + [self.view addSubview:videoProgressBar]; + [videoProgressBar autoPinWidthToSuperview]; + [videoProgressBar autoPinToTopLayoutGuideOfViewController:self withInset:0]; + CGFloat kVideoProgressBarHeight = 44; + [videoProgressBar autoSetDimension:ALDimensionHeight toSize:kVideoProgressBarHeight]; UIButton *playVideoButton = [UIButton new]; self.playVideoButton = playVideoButton; @@ -380,16 +378,13 @@ NS_ASSUME_NONNULL_BEGIN ]]; if (self.isVideo) { - // bar button video controls only work on iOS9+ - if (@available(iOS 9.0, *)) { - UIBarButtonItem *playerButton = isPlayingVideo ? self.videoPauseBarButton : self.videoPlayBarButton; - [toolbarItems addObjectsFromArray:@[ - playerButton, - [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace - target:nil - action:nil], - ]]; - } + UIBarButtonItem *playerButton = isPlayingVideo ? self.videoPauseBarButton : self.videoPlayBarButton; + [toolbarItems addObjectsFromArray:@[ + playerButton, + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace + target:nil + action:nil], + ]]; } [toolbarItems addObject:[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash @@ -443,24 +438,20 @@ NS_ASSUME_NONNULL_BEGIN OWSFail(@"%@ Missing video file: %@", self.logTag, self.attachmentStream.mediaURL); } - if (@available(iOS 9.0, *)) { - OWSVideoPlayer *player = [[OWSVideoPlayer alloc] initWithUrl:self.attachmentUrl]; - [player seekToTime:kCMTimeZero]; - player.delegate = self; - self.videoPlayer = player; + OWSVideoPlayer *player = [[OWSVideoPlayer alloc] initWithUrl:self.attachmentUrl]; + [player seekToTime:kCMTimeZero]; + player.delegate = self; + self.videoPlayer = player; - VideoPlayerView *playerView = [VideoPlayerView new]; - playerView.player = player.avPlayer; + VideoPlayerView *playerView = [VideoPlayerView new]; + playerView.player = player.avPlayer; - [NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultLow - forConstraints:^{ - [playerView autoSetDimensionsToSize:self.image.size]; - }]; + [NSLayoutConstraint autoSetPriority:UILayoutPriorityDefaultLow + forConstraints:^{ + [playerView autoSetDimensionsToSize:self.image.size]; + }]; - return playerView; - } else { - return [[UIImageView alloc] initWithImage:self.image]; - } + return playerView; } - (void)setAreToolbarsHidden:(BOOL)areToolbarsHidden @@ -895,18 +886,13 @@ NS_ASSUME_NONNULL_BEGIN - (void)playVideo { - if (@available(iOS 9, *)) { - OWSAssert(self.videoPlayer); + OWSAssert(self.videoPlayer); - [self updateFooterBarButtonItemsWithIsPlayingVideo:YES]; - self.playVideoButton.hidden = YES; - self.areToolbarsHidden = YES; + [self updateFooterBarButtonItemsWithIsPlayingVideo:YES]; + self.playVideoButton.hidden = YES; + self.areToolbarsHidden = YES; - [self.videoPlayer play]; - } else { - [self legacyPlayVideo]; - return; - } + [self.videoPlayer play]; } - (void)pauseVideo @@ -958,21 +944,6 @@ NS_ASSUME_NONNULL_BEGIN } } -#pragma mark iOS8 Video Playback - -// AVPlayer was introduced in iOS9, so on iOS8 we fall back to MPMoviePlayer -// This causes an unforutnate "double present" since we present the full screen view and then the MPMovie view over top. -// And similarly a double dismiss. -- (void)legacyPlayVideo -{ - if (@available(iOS 9.0, *)) { - OWSFail(@"legacy video is for iOS8 only"); - } - MPMoviePlayerViewController *vc = [[MPMoviePlayerViewController alloc] initWithContentURL:self.attachmentUrl]; - - [self presentViewController:vc animated:YES completion:nil]; -} - #pragma mark - Saving images to Camera Roll - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo diff --git a/Signal/src/call/CallAudioService.swift b/Signal/src/call/CallAudioService.swift index e2c621656..ae1f16eae 100644 --- a/Signal/src/call/CallAudioService.swift +++ b/Signal/src/call/CallAudioService.swift @@ -453,13 +453,7 @@ protocol CallAudioServiceDelegate: class { var availableInputs: [AudioSource] { guard let availableInputs = avAudioSession.availableInputs else { // I'm not sure why this would happen, but it may indicate an error. - // In practice, I haven't seen it on iOS9+. - // - // I *have* seen it on iOS8, but it doesn't seem to cause any problems, - // so we do *not* trigger the assert on that platform. - if #available(iOS 9.0, *) { - owsFail("No available inputs or inputs not ready") - } + owsFail("No available inputs or inputs not ready") return [AudioSource.builtInSpeaker] } diff --git a/Signal/src/network/PushManager.m b/Signal/src/network/PushManager.m index 6788c1526..264b7859d 100644 --- a/Signal/src/network/PushManager.m +++ b/Signal/src/network/PushManager.m @@ -300,12 +300,8 @@ NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRe action_reply.title = NSLocalizedString(@"PUSH_MANAGER_REPLY", @""); action_reply.destructive = NO; action_reply.authenticationRequired = NO; - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(9, 0)) { - action_reply.behavior = UIUserNotificationActionBehaviorTextInput; - action_reply.activationMode = UIUserNotificationActivationModeBackground; - } else { - action_reply.activationMode = UIUserNotificationActivationModeForeground; - } + action_reply.behavior = UIUserNotificationActionBehaviorTextInput; + action_reply.activationMode = UIUserNotificationActivationModeBackground; UIMutableUserNotificationCategory *messageCategory = [UIMutableUserNotificationCategory new]; messageCategory.identifier = Signal_Full_New_Message_Category; diff --git a/Signal/src/views/ContactCell.swift b/Signal/src/views/ContactCell.swift index 7b498d0ac..5875a230a 100644 --- a/Signal/src/views/ContactCell.swift +++ b/Signal/src/views/ContactCell.swift @@ -1,15 +1,14 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // import UIKit import Contacts import SignalServiceKit -@available(iOS 9.0, *) class ContactCell: UITableViewCell { - static let nib = UINib(nibName:"ContactCell", bundle: nil) + static let nib = UINib(nibName: "ContactCell", bundle: nil) @IBOutlet weak var contactTextLabel: UILabel! @IBOutlet weak var contactDetailTextLabel: UILabel! @@ -48,7 +47,7 @@ class ContactCell: UITableViewCell { self.contact = contact if contactTextLabel != nil { - contactTextLabel.attributedText = contact.cnContact?.formattedFullName(font:contactTextLabel.font) + contactTextLabel.attributedText = contact.cnContact?.formattedFullName(font: contactTextLabel.font) } updateSubtitleBasedonType(subtitleType, contact: contact) @@ -65,7 +64,7 @@ class ContactCell: UITableViewCell { let avatarBuilder = OWSContactAvatarBuilder(nonSignalName: contact.fullName, colorSeed: contactIdForDeterminingBackgroundColor, diameter: kAvatarWidth, - contactsManager:contactsManager) + contactsManager: contactsManager) self.contactImageView?.image = avatarBuilder.buildDefaultImage() } else { @@ -92,7 +91,6 @@ class ContactCell: UITableViewCell { } } -@available(iOS 9.0, *) fileprivate extension CNContact { /** * Bold the sorting portion of the name. e.g. if we sort by family name, bold the family name. @@ -102,7 +100,7 @@ fileprivate extension CNContact { let boldDescriptor = font.fontDescriptor.withSymbolicTraits(.traitBold) let boldAttributes = [ - NSFontAttributeName: UIFont(descriptor:boldDescriptor!, size: 0) + NSFontAttributeName: UIFont(descriptor: boldDescriptor!, size: 0) ] if let attributedName = CNContactFormatter.attributedString(from: self, style: .fullName, defaultAttributes: nil) { diff --git a/Signal/src/views/MarqueeLabel.swift b/Signal/src/views/MarqueeLabel.swift index 5b082c61a..46df0bdbb 100644 --- a/Signal/src/views/MarqueeLabel.swift +++ b/Signal/src/views/MarqueeLabel.swift @@ -1,10 +1,5 @@ -// Grabbed from: https://github.com/cbpowell/MarqueeLabel-Swift/blob/cd331f3cfc3f9d7114ffa5aa4f243f1d5eda9d0d/Classes/MarqueeLabel.swift -// License: MIT License // -// MarqueeLabel.swift -// -// Created by Charles Powell on 8/6/14. -// Copyright (c) 2015 Charles Powell. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // import UIKit @@ -13,7 +8,7 @@ import QuartzCore @IBDesignable open class MarqueeLabel: UILabel, CAAnimationDelegate { - + /** An enum that defines the types of `MarqueeLabel` scrolling @@ -32,11 +27,11 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { case continuous case continuousReverse } - + // // MARK: - Public properties // - + /** Defines the direction and method in which the `MarqueeLabel` instance scrolls. `MarqueeLabel` supports six default types of scrolling: `Left`, `LeftRight`, `Right`, `RightLeft`, `Continuous`, and `ContinuousReverse`. @@ -61,7 +56,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { updateAndScroll() } } - + /** An optional custom scroll "sequence", defined by an array of `ScrollStep` or `FadeStep` instances. A sequence defines a single scroll/animation loop, which will continue to be automatically repeated like the default types. @@ -85,7 +80,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { - SeeAlso: FadeStep */ open var scrollSequence: Array? - + /** Specifies the animation curve used in the scrolling motion of the labels. Allowable options: @@ -98,7 +93,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { Defaults to `UIViewAnimationOptionCurveEaseInOut`. */ open var animationCurve: UIViewAnimationCurve = .linear - + /** A boolean property that sets whether the `MarqueeLabel` should behave like a normal `UILabel`. @@ -124,7 +119,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } } } - + /** A boolean property that sets whether the `MarqueeLabel` should hold (prevent) automatic label scrolling. @@ -147,7 +142,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } } } - + /** A boolean property that sets whether the `MarqueeLabel` should only begin a scroll when tapped. @@ -175,7 +170,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } } } - + /** A read-only boolean property that indicates if the label's scroll animation has been paused. @@ -185,7 +180,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { open var isPaused: Bool { return (sublabel.layer.speed == 0.0) } - + /** A boolean property that indicates if the label is currently away from the home location. @@ -195,10 +190,10 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { if let presentationLayer = sublabel.layer.presentation() { return !(presentationLayer.position.x == homeLabelFrame.origin.x) } - + return false } - + /** The `MarqueeLabel` scrolling speed may be defined by one of two ways: - Rate(CGFloat): The speed is defined by a rate of motion, in units of points per second. @@ -209,7 +204,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { public enum SpeedLimit { case rate(CGFloat) case duration(CGFloat) - + var value: CGFloat { switch self { case .rate(let rate): @@ -219,7 +214,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } } } - + /** Defines the speed of the `MarqueeLabel` scrolling animation. @@ -239,7 +234,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } } } - + @available(*, deprecated : 2.6, message : "Use speed property instead") @IBInspectable open var scrollDuration: CGFloat { get { @@ -252,7 +247,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { speed = .duration(newValue) } } - + @available(*, deprecated : 2.6, message : "Use speed property instead") @IBInspectable open var scrollRate: CGFloat { get { @@ -265,7 +260,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { speed = .rate(newValue) } } - + /** A buffer (offset) between the leading edge of the label text and the label frame. @@ -289,7 +284,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } } } - + /** A buffer (offset) between the trailing edge of the label text and the label frame. @@ -313,7 +308,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } } } - + /** The length of transparency fade at the left and right edges of the frame. @@ -331,25 +326,23 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } } } - - + /** The length of delay in seconds that the label pauses at the completion of a scroll. */ @IBInspectable open var animationDelay: CGFloat = 1.0 - - + /** The read-only duration of the scroll animation (not including delay). The value of this property is calculated from the value set to the `speed` property. If a .duration value is used to set the label animation speed, this value will be equivalent. */ private(set) public var animationDuration: CGFloat = 0.0 - + // // MARK: - Class Functions and Helpers // - + /** Convenience method to restart all `MarqueeLabel` instances that have the specified view controller in their next responder chain. @@ -368,7 +361,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { open class func restartLabelsOfController(_ controller: UIViewController) { MarqueeLabel.notifyController(controller, message: .Restart) } - + /** Convenience method to restart all `MarqueeLabel` instances that have the specified view controller in their next responder chain. @@ -381,7 +374,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { open class func controllerViewWillAppear(_ controller: UIViewController) { MarqueeLabel.restartLabelsOfController(controller) } - + /** Convenience method to restart all `MarqueeLabel` instances that have the specified view controller in their next responder chain. @@ -394,7 +387,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { open class func controllerViewDidAppear(_ controller: UIViewController) { MarqueeLabel.restartLabelsOfController(controller) } - + /** Labelizes all `MarqueeLabel` instances that have the specified view controller in their next responder chain. @@ -406,7 +399,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { open class func controllerLabelsLabelize(_ controller: UIViewController) { MarqueeLabel.notifyController(controller, message: .Labelize) } - + /** De-labelizes all `MarqueeLabel` instances that have the specified view controller in their next responder chain. @@ -419,11 +412,10 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { MarqueeLabel.notifyController(controller, message: .Animate) } - // // MARK: - Initialization // - + /** Returns a newly initialized `MarqueeLabel` instance with the specified scroll rate and edge transparency fade length. @@ -439,7 +431,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { super.init(frame: frame) setup() } - + /** Returns a newly initialized `MarqueeLabel` instance with the specified scroll rate and edge transparency fade length. @@ -455,12 +447,12 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { super.init(frame: frame) setup() } - + required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() } - + /** Returns a newly initialized `MarqueeLabel` instance. @@ -470,9 +462,9 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { - Returns: An initialized `MarqueeLabel` object or nil if the object couldn't be created. */ convenience public override init(frame: CGRect) { - self.init(frame: frame, duration:7.0, fadeLength:0.0) + self.init(frame: frame, duration: 7.0, fadeLength: 0.0) } - + private func setup() { // Create sublabel sublabel = UILabel(frame: self.bounds) @@ -481,11 +473,11 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { // Add sublabel addSubview(sublabel) - + // Configure self super.clipsToBounds = true super.numberOfLines = 1 - + // Add notification observers // Custom class notifications NotificationCenter.default.addObserver(self, selector: #selector(MarqueeLabel.restartForViewController(_:)), name: NSNotification.Name(rawValue: MarqueeKeys.Restart.rawValue), object: nil) @@ -495,32 +487,31 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { NotificationCenter.default.addObserver(self, selector: #selector(MarqueeLabel.restartLabel), name: NSNotification.Name.OWSApplicationDidBecomeActive, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(MarqueeLabel.shutdownLabel), name: NSNotification.Name.OWSApplicationDidEnterBackground, object: nil) } - + override open func awakeFromNib() { super.awakeFromNib() forwardPropertiesToSublabel() } - - @available(iOS 8.0, *) + override open func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() forwardPropertiesToSublabel() } - + private func forwardPropertiesToSublabel() { /* Note that this method is currently ONLY called from awakeFromNib, i.e. when text properties are set via a Storyboard. As the Storyboard/IB doesn't currently support attributed strings, there's no need to "forward" the super attributedString value. */ - + // Since we're a UILabel, we actually do implement all of UILabel's properties. // We don't care about these values, we just want to forward them on to our sublabel. let properties = ["baselineAdjustment", "enabled", "highlighted", "highlightedTextColor", "minimumFontSize", "shadowOffset", "textAlignment", "userInteractionEnabled", "adjustsFontSizeToFitWidth", "lineBreakMode", "numberOfLines", "contentMode"] - + // Iterate through properties sublabel.text = super.text sublabel.font = super.font @@ -533,14 +524,14 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { sublabel.setValue(value, forKeyPath: prop) } } - + // // MARK: - MarqueeLabel Heavy Lifting // override open func layoutSubviews() { super.layoutSubviews() - + updateAndScroll(true) } @@ -549,7 +540,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { shutdownLabel() } } - + override open func didMoveToWindow() { if self.window == nil { shutdownLabel() @@ -557,33 +548,33 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { updateAndScroll() } } - + private func updateAndScroll() { updateAndScroll(true) } - + private func updateAndScroll(_ shouldBeginScroll: Bool) { // Check if scrolling can occur if !labelReadyForScroll() { return } - + // Calculate expected size let expectedLabelSize = sublabelSize() - + // Invalidate intrinsic size invalidateIntrinsicContentSize() - + // Move label to home returnLabelToHome() - + // Check if label should scroll // Note that the holdScrolling propery does not affect this if !labelShouldScroll() { // Set text alignment and break mode to act like a normal label sublabel.textAlignment = super.textAlignment sublabel.lineBreakMode = super.lineBreakMode - + let labelFrame: CGRect switch type { case .continuousReverse, .rightLeft: @@ -591,24 +582,24 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { default: labelFrame = CGRect(x: leadingBuffer, y: 0.0, width: bounds.size.width - leadingBuffer, height: bounds.size.height).integral } - + homeLabelFrame = labelFrame awayOffset = 0.0 - + // Remove an additional sublabels (for continuous types) - repliLayer?.instanceCount = 1; - + repliLayer?.instanceCount = 1 + // Set the sublabel frame to calculated labelFrame sublabel.frame = labelFrame - + // Remove fade, as by definition none is needed in this case removeGradientMask() - + return } - + // Label DOES need to scroll - + // Recompute the animation duration animationDuration = { switch self.speed { @@ -618,13 +609,13 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { return duration } }() - + // Spacing between primary and second sublabel must be at least equal to leadingBuffer, and at least equal to the fadeLength let minTrailing = max(max(leadingBuffer, trailingBuffer), fadeLength) - + // Determine positions and generate scroll steps let sequence: [MarqueeStep] - + switch type { case .continuous, .continuousReverse: if (type == .continuous) { @@ -634,7 +625,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { homeLabelFrame = CGRect(x: bounds.size.width - (expectedLabelSize.width + leadingBuffer), y: 0.0, width: expectedLabelSize.width, height: bounds.size.height).integral awayOffset = (homeLabelFrame.size.width + minTrailing) } - + // Find when the lead label will be totally offscreen let offsetDistance = awayOffset let offscreenAmount = homeLabelFrame.size.width @@ -642,7 +633,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { // Find when the animation will hit that point let startFadeTimeFraction = timingFunctionForAnimationCurve(animationCurve).durationPercentageForPositionPercentage(startFadeFraction, duration: (animationDelay + animationDuration)) let startFadeTime = startFadeTimeFraction * animationDuration - + sequence = scrollSequence ?? [ ScrollStep(timeStep: 0.0, position: .home, edgeFades: .trailing), // Starting point, at home, with trailing fade ScrollStep(timeStep: animationDelay, position: .home, edgeFades: .trailing), // Delay at home, maintaining fade state @@ -652,14 +643,14 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { ScrollStep(timeStep: animationDuration, timingFunction: animationCurve, // Ending point (back at home), with animationCurve transition, with trailing fade position: .away, edgeFades: .trailing) ] - + // Set frame and text sublabel.frame = homeLabelFrame - + // Configure replication repliLayer?.instanceCount = 2 repliLayer?.instanceTransform = CATransform3DMakeTranslation(-awayOffset, 0.0, 0.0) - + case .leftRight, .left, .rightLeft, .right: if (type == .leftRight || type == .left) { homeLabelFrame = CGRect(x: leadingBuffer, y: 0.0, width: expectedLabelSize.width, height: bounds.size.height).integral @@ -674,10 +665,10 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } // Set frame and text sublabel.frame = homeLabelFrame - + // Remove any replication repliLayer?.instanceCount = 1 - + if (type == .leftRight || type == .rightLeft) { sequence = scrollSequence ?? [ ScrollStep(timeStep: 0.0, position: .home, edgeFades: .trailing), // Starting point, at home, with trailing fade @@ -701,27 +692,25 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { ScrollStep(timeStep: animationDuration, timingFunction: animationCurve, // Away position, using animationCurve transition, with only leading edge faded in position: .away, edgeFades: .leading), ScrollStep(timeStep: 60*60*24*365.0, // "Delay" at away, for huge time to effectie stay at away permanently - position: .away, edgeFades: .leading), + position: .away, edgeFades: .leading) ] } } - - - + // Configure gradient for current condition applyGradientMask(fadeLength, animated: !self.labelize) - + if !tapToScroll && !holdScrolling && shouldBeginScroll { beginScroll(sequence) } } - + private func sublabelSize() -> CGSize { // Bound the expected size let maximumLabelSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) // Calculate the expected size var expectedLabelSize = sublabel.sizeThatFits(maximumLabelSize) - + #if os(tvOS) // Sanitize width to 16384.0 (largest width a UILabel will draw on tvOS) expectedLabelSize.width = min(expectedLabelSize.width, 16384.0) @@ -734,45 +723,45 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { expectedLabelSize.height = bounds.size.height return expectedLabelSize } - + override open func sizeThatFits(_ size: CGSize) -> CGSize { var fitSize = sublabel.sizeThatFits(size) fitSize.width += leadingBuffer return fitSize } - + // // MARK: - Animation Handling // - + open func labelShouldScroll() -> Bool { // Check for nil string if sublabel.text == nil { return false } - + // Check for empty string if sublabel.text!.isEmpty { return false } - + // Check if the label string fits let labelTooLarge = (sublabelSize().width + leadingBuffer) > self.bounds.size.width + CGFloat.ulpOfOne let animationHasDuration = speed.value > 0.0 return (!labelize && labelTooLarge && animationHasDuration) } - + private func labelReadyForScroll() -> Bool { // Check if we have a superview if superview == nil { return false } - + // Check if we are attached to a window if window == nil { return false } - + // Check if our view controller is ready let viewController = firstAvailableViewController() if viewController != nil { @@ -780,28 +769,28 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { return false } } - + return true } - + private func returnLabelToHome() { // Remove any gradient animation maskLayer?.removeAllAnimations() - + // Remove all sublabel position animations sublabel.layer.removeAllAnimations() - + // Remove completion block scrollCompletionBlock = nil } - + private func beginScroll(_ sequence: [MarqueeStep]) { let scroller = generateScrollAnimation(sequence) let fader = generateGradientAnimation(sequence, totalDuration: scroller.duration) - + scroll(scroller, fader: fader) } - + private func scroll(_ scroller: MLAnimation, fader: MLAnimation?) { // Check for conditions which would prevent scrolling if !labelReadyForScroll() { @@ -809,14 +798,14 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } // Convert fader to var var fader = fader - + // Call pre-animation hook labelWillBeginScroll() - + // Start animation transactions CATransaction.begin() CATransaction.setAnimationDuration(TimeInterval(scroller.duration)) - + // Create gradient animation, if needed let gradientAnimation: CAKeyframeAnimation? // Check for IBDesignable @@ -827,14 +816,14 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { maskLayer?.colors = finalColors } maskLayer?.removeAnimation(forKey: "setupFade") - + // Generate animation if needed if let previousAnimation = fader?.anim { gradientAnimation = previousAnimation } else { gradientAnimation = nil } - + // Apply fade animation maskLayer?.add(gradientAnimation!, forKey: "gradient") } else { @@ -842,22 +831,22 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { fader = nil } #else - fader = nil; + fader = nil #endif - - scrollCompletionBlock = { [weak self] (finished: Bool) -> () in + + scrollCompletionBlock = { [weak self] (finished: Bool) -> Void in guard finished else { // Do not continue into the next loop return } - + guard (self != nil) else { return } - + // Call returned home function self!.labelReturnedToHome(true) - + // Check to ensure that: // 1) We don't double fire if an animation already exists // 2) The instance is still attached to a window - this completion block is called for @@ -866,45 +855,45 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { guard self!.window != nil else { return } - + guard self!.sublabel.layer.animation(forKey: "position") == nil else { return } - + // Begin again, if conditions met if (self!.labelShouldScroll() && !self!.tapToScroll && !self!.holdScrolling) { // Perform completion callback self!.scroll(scroller, fader: fader) } } - + // Perform scroll animation scroller.anim.setValue(true, forKey: MarqueeKeys.CompletionClosure.rawValue) scroller.anim.delegate = self sublabel.layer.add(scroller.anim, forKey: "position") - + CATransaction.commit() } - + private func generateScrollAnimation(_ sequence: [MarqueeStep]) -> MLAnimation { // Create scroller, which defines the animation to perform let homeOrigin = homeLabelFrame.origin let awayOrigin = offsetCGPoint(homeLabelFrame.origin, offset: awayOffset) - + let scrollSteps = sequence.filter({ $0 is ScrollStep }) as! [ScrollStep] let totalDuration = scrollSteps.reduce(0.0) { $0 + $1.timeStep } - + // Build scroll data var totalTime: CGFloat = 0.0 var scrollKeyTimes = [NSNumber]() var scrollKeyValues = [NSValue]() var scrollTimingFunctions = [CAMediaTimingFunction]() - + for (offset, step) in scrollSteps.enumerated() { // Scroll Times totalTime += step.timeStep - scrollKeyTimes.append(NSNumber(value:Float(totalTime/totalDuration))) - + scrollKeyTimes.append(NSNumber(value: Float(totalTime/totalDuration))) + // Scroll Values let scrollPosition: CGPoint switch step.position { @@ -915,24 +904,24 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { case .partial(let frac): scrollPosition = offsetCGPoint(homeOrigin, offset: awayOffset*frac) } - scrollKeyValues.append(NSValue(cgPoint:scrollPosition)) - + scrollKeyValues.append(NSValue(cgPoint: scrollPosition)) + // Scroll Timing Functions // Only need n-1 timing functions, so discard the first value as it's unused if offset == 0 { continue } scrollTimingFunctions.append(timingFunctionForAnimationCurve(step.timingFunction)) } - + // Create animation let animation = CAKeyframeAnimation(keyPath: "position") // Set values animation.keyTimes = scrollKeyTimes animation.values = scrollKeyValues animation.timingFunctions = scrollTimingFunctions - + return (anim: animation, duration: totalDuration) } - + private func generateGradientAnimation(_ sequence: [MarqueeStep], totalDuration: CGFloat) -> MLAnimation { // Setup var totalTime: CGFloat = 0.0 @@ -942,26 +931,26 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { var fadeTimingFunctions = [CAMediaTimingFunction]() let transp = UIColor.clear.cgColor let opaque = UIColor.black.cgColor - + // Filter to get only scroll steps and valid precedent/subsequent fade steps let fadeSteps = sequence.enumerated().filter { (arg: (offset: Int, element: MarqueeStep)) -> Bool in let (offset, element) = arg - + // Include all Scroll Steps if element is ScrollStep { return true } - + // Include all Fade Steps that have a directly preceding or subsequent Scroll Step // Exception: Fade Step cannot be first step if offset == 0 { return false } - + // Subsequent step if 1) positive/zero time step and 2) follows a Scroll Step let subsequent = element.timeStep >= 0 && (sequence[max(0, offset - 1)] is ScrollStep) // Precedent step if 1) negative time step and 2) precedes a Scroll Step let precedent = element.timeStep < 0 && (sequence[min(sequence.count - 1, offset + 1)] is ScrollStep) - + return (precedent || subsequent) } - + for (offset, step) in fadeSteps { // Fade times if (step is ScrollStep) { @@ -976,8 +965,8 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { stepTime = totalTime + fadeSteps[offset + 1].element.timeStep + step.timeStep } } - fadeKeyTimes.append(NSNumber(value:Float(stepTime/totalDuration))) - + fadeKeyTimes.append(NSNumber(value: Float(stepTime/totalDuration))) + // Fade Values let values: [CGColor] let leading = step.edgeFades.contains(.leading) ? transp : opaque @@ -989,37 +978,37 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { values = [trailing, opaque, opaque, leading] } fadeKeyValues.append(values) - + // Fade Timing Function // Only need n-1 timing functions, so discard the first value as it's unused if offset == 0 { continue } fadeTimingFunctions.append(timingFunctionForAnimationCurve(step.timingFunction)) } - + // Create new animation let animation = CAKeyframeAnimation(keyPath: "colors") - + animation.values = fadeKeyValues animation.keyTimes = fadeKeyTimes animation.timingFunctions = fadeTimingFunctions - - return (anim: animation, duration: max(totalTime,totalDuration)) + + return (anim: animation, duration: max(totalTime, totalDuration)) } - + private func applyGradientMask(_ fadeLength: CGFloat, animated: Bool, firstStep: MarqueeStep? = nil) { // Remove any in-flight animations maskLayer?.removeAllAnimations() - + // Check for zero-length fade if (fadeLength <= 0.0) { removeGradientMask() return } - + // Configure gradient mask without implicit animations CATransaction.begin() CATransaction.setDisableActions(true) - + // Determine if gradient mask needs to be created let gradientMask: CAGradientLayer if let currentMask = self.maskLayer { @@ -1030,10 +1019,10 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { gradientMask = CAGradientLayer() gradientMask.shouldRasterize = true gradientMask.rasterizationScale = UIScreen.main.scale - gradientMask.startPoint = CGPoint(x:0.0, y:0.5) - gradientMask.endPoint = CGPoint(x:1.0, y:0.5) + gradientMask.startPoint = CGPoint(x: 0.0, y: 0.5) + gradientMask.endPoint = CGPoint(x: 1.0, y: 0.5) } - + // Check if there is a mask to layer size mismatch if gradientMask.bounds != self.layer.bounds { // Adjust stops based on fade length @@ -1041,41 +1030,41 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { let rightFadeStop = 1.0 - fadeLength/self.bounds.size.width gradientMask.locations = [0.0, leftFadeStop, rightFadeStop, 1.0].map { NSNumber(value: Float($0)) } } - + gradientMask.bounds = self.layer.bounds - gradientMask.position = CGPoint(x:self.bounds.midX, y:self.bounds.midY) - + gradientMask.position = CGPoint(x: self.bounds.midX, y: self.bounds.midY) + // Set up colors let transparent = UIColor.clear.cgColor let opaque = UIColor.black.cgColor - + // Set mask self.layer.mask = gradientMask - + // Determine colors for non-scrolling label (i.e. at home) let adjustedColors: [CGColor] let trailingFadeNeeded = self.labelShouldScroll() - + switch (type) { case .continuousReverse, .rightLeft: adjustedColors = [(trailingFadeNeeded ? transparent : opaque), opaque, opaque, opaque] - + // .Continuous, .LeftRight default: adjustedColors = [opaque, opaque, opaque, (trailingFadeNeeded ? transparent : opaque)] } - + // Check for IBDesignable #if TARGET_INTERFACE_BUILDER gradientMask.colors = adjustedColors CATransaction.commit() return #endif - + if (animated) { // Finish transaction CATransaction.commit() - + // Create animation for color change let colorAnimation = GradientSetupAnimation(keyPath: "colors") colorAnimation.fromValue = gradientMask.colors @@ -1089,14 +1078,14 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { CATransaction.commit() } } - + private func removeGradientMask() { self.layer.mask = nil } - + private func timingFunctionForAnimationCurve(_ curve: UIViewAnimationCurve) -> CAMediaTimingFunction { let timingFunction: String? - + switch curve { case .easeIn: timingFunction = kCAMediaTimingFunctionEaseIn @@ -1107,10 +1096,10 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { default: timingFunction = kCAMediaTimingFunctionLinear } - + return CAMediaTimingFunction(name: timingFunction!) } - + private func transactionDurationType(_ labelType: MarqueeType, interval: CGFloat, delay: CGFloat) -> TimeInterval { switch (labelType) { case .leftRight, .rightLeft: @@ -1119,7 +1108,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { return TimeInterval(delay + interval) } } - + public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { if let setupAnim = anim as? GradientSetupAnimation { if let finalColors = setupAnim.toValue as? [CGColor] { @@ -1131,53 +1120,52 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { scrollCompletionBlock?(flag) } } - - + // // MARK: - Private details // - + private var sublabel = UILabel() - + fileprivate var homeLabelFrame = CGRect.zero fileprivate var awayOffset: CGFloat = 0.0 - + override open class var layerClass: AnyClass { return CAReplicatorLayer.self } - + fileprivate weak var repliLayer: CAReplicatorLayer? { return self.layer as? CAReplicatorLayer } - + fileprivate weak var maskLayer: CAGradientLayer? { return self.layer.mask as! CAGradientLayer? } - + fileprivate var scrollCompletionBlock: MLAnimationCompletionBlock? - + override open func draw(_ layer: CALayer, in ctx: CGContext) { // Do NOT call super, to prevent UILabel superclass from drawing into context // Label drawing is handled by sublabel and CAReplicatorLayer layer class - + // Draw only background color if let bgColor = backgroundColor { - ctx.setFillColor(bgColor.cgColor); - ctx.fill(layer.bounds); + ctx.setFillColor(bgColor.cgColor) + ctx.fill(layer.bounds) } } - + fileprivate enum MarqueeKeys: String { case Restart = "MLViewControllerRestart" case Labelize = "MLShouldLabelize" case Animate = "MLShouldAnimate" case CompletionClosure = "MLAnimationCompletion" } - + class fileprivate func notifyController(_ controller: UIViewController, message: MarqueeKeys) { NotificationCenter.default.post(name: Notification.Name(rawValue: message.rawValue), object: nil, userInfo: ["controller" : controller]) } - + @objc public func restartForViewController(_ notification: Notification) { if let controller = (notification as NSNotification).userInfo?["controller"] as? UIViewController { if controller === self.firstAvailableViewController() { @@ -1185,7 +1173,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } } } - + @objc public func labelizeForController(_ notification: Notification) { if let controller = (notification as NSNotification).userInfo?["controller"] as? UIViewController { if controller === self.firstAvailableViewController() { @@ -1193,7 +1181,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } } } - + @objc public func animateForController(_ notification: Notification) { if let controller = (notification as NSNotification).userInfo?["controller"] as? UIViewController { if controller === self.firstAvailableViewController() { @@ -1201,12 +1189,11 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { } } } - - + // // MARK: - Label Control // - + /** Overrides any non-size condition which is preventing the receiver from automatically scrolling, and begins a scroll animation. @@ -1226,7 +1213,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { updateAndScroll() } } - + /** Immediately resets the label to the home position, cancelling any in-flight scroll animation, and restarts the scroll animation if the appropriate conditions are met. @@ -1241,7 +1228,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { updateAndScroll() } } - + /** Resets the label text, recalculating the scroll animation. @@ -1256,7 +1243,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { homeLabelFrame = CGRect.null awayOffset = 0.0 } - + /** Immediately resets the label to the home position, cancelling any in-flight scroll animation. @@ -1273,7 +1260,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { // Apply gradient mask for home location applyGradientMask(fadeLength, animated: false) } - + /** Pauses the text scrolling animation, at any point during an in-progress animation. @@ -1287,18 +1274,18 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { guard (!isPaused && awayFromHome) else { return } - + // Pause sublabel position animations let labelPauseTime = sublabel.layer.convertTime(CACurrentMediaTime(), from: nil) sublabel.layer.speed = 0.0 sublabel.layer.timeOffset = labelPauseTime - + // Pause gradient fade animation - let gradientPauseTime = maskLayer?.convertTime(CACurrentMediaTime(), from:nil) + let gradientPauseTime = maskLayer?.convertTime(CACurrentMediaTime(), from: nil) maskLayer?.speed = 0.0 maskLayer?.timeOffset = gradientPauseTime! } - + /** Un-pauses a previously paused text scrolling animation. This method has no effect if the label was not previously paused using `pauseLabel`. @@ -1309,28 +1296,28 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { guard (isPaused) else { return } - + // Unpause sublabel position animations let labelPausedTime = sublabel.layer.timeOffset sublabel.layer.speed = 1.0 sublabel.layer.timeOffset = 0.0 sublabel.layer.beginTime = 0.0 - sublabel.layer.beginTime = sublabel.layer.convertTime(CACurrentMediaTime(), from:nil) - labelPausedTime - + sublabel.layer.beginTime = sublabel.layer.convertTime(CACurrentMediaTime(), from: nil) - labelPausedTime + // Unpause gradient fade animation let gradientPauseTime = maskLayer?.timeOffset maskLayer?.speed = 1.0 maskLayer?.timeOffset = 0.0 maskLayer?.beginTime = 0.0 - maskLayer?.beginTime = maskLayer!.convertTime(CACurrentMediaTime(), from:nil) - gradientPauseTime! + maskLayer?.beginTime = maskLayer!.convertTime(CACurrentMediaTime(), from: nil) - gradientPauseTime! } - + @objc public func labelWasTapped(_ recognizer: UIGestureRecognizer) { if labelShouldScroll() && !awayFromHome { updateAndScroll() } } - + /** Called when the label animation is about to begin. @@ -1341,7 +1328,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { // Default implementation does nothing - override to customize return } - + /** Called when the label animation has finished, and the label is at the home position. @@ -1357,17 +1344,17 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { // Default implementation does nothing - override to customize return } - + // // MARK: - Modified UILabel Functions/Getters/Setters // - + #if os(iOS) override open func forBaselineLayout() -> UIView { // Use subLabel view for handling baseline layouts return sublabel } - + override open var forLastBaselineLayout: UIView { // Use subLabel view for handling baseline layouts return sublabel @@ -1378,7 +1365,7 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { get { return sublabel.text } - + set { if sublabel.text == newValue { return @@ -1388,12 +1375,12 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { super.text = text } } - + override open var attributedText: NSAttributedString? { get { return sublabel.attributedText } - + set { if sublabel.attributedText == newValue { return @@ -1403,195 +1390,193 @@ open class MarqueeLabel: UILabel, CAAnimationDelegate { super.attributedText = attributedText } } - + override open var font: UIFont! { get { return sublabel.font } - + set { if sublabel.font == newValue { return } sublabel.font = newValue super.font = newValue - + updateAndScroll() } } - + override open var textColor: UIColor! { get { return sublabel.textColor } - + set { sublabel.textColor = newValue super.textColor = newValue } } - + override open var backgroundColor: UIColor? { get { return sublabel.backgroundColor } - + set { sublabel.backgroundColor = newValue super.backgroundColor = newValue } } - + override open var shadowColor: UIColor? { get { return sublabel.shadowColor } - + set { sublabel.shadowColor = newValue super.shadowColor = newValue } } - + override open var shadowOffset: CGSize { get { return sublabel.shadowOffset } - + set { sublabel.shadowOffset = newValue super.shadowOffset = newValue } } - + override open var highlightedTextColor: UIColor? { get { return sublabel.highlightedTextColor } - + set { sublabel.highlightedTextColor = newValue super.highlightedTextColor = newValue } } - + override open var isHighlighted: Bool { get { return sublabel.isHighlighted } - + set { sublabel.isHighlighted = newValue super.isHighlighted = newValue } } - + override open var isEnabled: Bool { get { return sublabel.isEnabled } - + set { sublabel.isEnabled = newValue super.isEnabled = newValue } } - + override open var numberOfLines: Int { get { return super.numberOfLines } - + set { // By the nature of MarqueeLabel, this is 1 super.numberOfLines = 1 } } - + override open var adjustsFontSizeToFitWidth: Bool { get { return super.adjustsFontSizeToFitWidth } - + set { // By the nature of MarqueeLabel, this is false super.adjustsFontSizeToFitWidth = false } } - + override open var minimumScaleFactor: CGFloat { get { return super.minimumScaleFactor } - + set { super.minimumScaleFactor = 0.0 } } - + override open var baselineAdjustment: UIBaselineAdjustment { get { return sublabel.baselineAdjustment } - + set { sublabel.baselineAdjustment = newValue super.baselineAdjustment = newValue } } - + override open var intrinsicContentSize: CGSize { var content = sublabel.intrinsicContentSize content.width += leadingBuffer return content } - + override open var tintColor: UIColor! { get { return sublabel.tintColor } - + set { sublabel.tintColor = newValue super.tintColor = newValue } } - + override open func tintColorDidChange() { super.tintColorDidChange() sublabel.tintColorDidChange() } - + override open var contentMode: UIViewContentMode { get { return sublabel.contentMode } - + set { super.contentMode = contentMode sublabel.contentMode = newValue } } - // // MARK: - Support // - + fileprivate func offsetCGPoint(_ point: CGPoint, offset: CGFloat) -> CGPoint { return CGPoint(x: point.x + offset, y: point.y) } - + // // MARK: - Deinit // - + deinit { NotificationCenter.default.removeObserver(self) } - -} +} // // MARK: - Support @@ -1602,7 +1587,6 @@ public protocol MarqueeStep { var edgeFades: EdgeFade { get } } - /** `ScrollStep` types define the label position at a specified time delta since the last `ScrollStep` step, as well as the animation curve to that position and edge fade state at the position @@ -1626,25 +1610,25 @@ public struct ScrollStep: MarqueeStep { case away case partial(CGFloat) } - + /** The desired time between this step and the previous `ScrollStep` in a sequence. */ public let timeStep: CGFloat - + /** The animation curve to utilize between the previous `ScrollStep` in a sequence and this step. - Note: The animation curve value for the first `ScrollStep` in a sequence has no effect. */ public let timingFunction: UIViewAnimationCurve - + /** The position of the label for this scroll step. - SeeAlso: Position */ public let position: Position - + /** The option set defining the edge fade state for this scroll step. @@ -1652,7 +1636,7 @@ public struct ScrollStep: MarqueeStep { the direction of scroll) and trailing edge of the label. */ public let edgeFades: EdgeFade - + public init(timeStep: CGFloat, timingFunction: UIViewAnimationCurve = .linear, position: Position, edgeFades: EdgeFade) { self.timeStep = timeStep self.position = position @@ -1661,7 +1645,6 @@ public struct ScrollStep: MarqueeStep { } } - /** `FadeStep` types allow additional edge fade state definitions, around the states defined by the `ScrollStep` steps of a sequence. `FadeStep` steps are defined by the time delta to the preceding or subsequent `ScrollStep` step and the timing @@ -1683,12 +1666,12 @@ public struct FadeStep: MarqueeStep { a `ScrollStep`. */ public let timeStep: CGFloat - + /** The animation curve to utilize between the previous fade state in a sequence and this step. */ public let timingFunction: UIViewAnimationCurve - + /** The option set defining the edge fade state for this fade step. @@ -1698,7 +1681,7 @@ public struct FadeStep: MarqueeStep { As an Option Set type, both edge fade states may be defined using an array literal: `[.leading, .trailing]`. */ public let edgeFades: EdgeFade - + public init(timeStep: CGFloat, timingFunction: UIViewAnimationCurve = .linear, edgeFades: EdgeFade) { self.timeStep = timeStep self.timingFunction = timingFunction @@ -1706,32 +1689,32 @@ public struct FadeStep: MarqueeStep { } } -public struct EdgeFade : OptionSet { +public struct EdgeFade: OptionSet { public let rawValue: Int public static let leading = EdgeFade(rawValue: 1 << 0) public static let trailing = EdgeFade(rawValue: 1 << 1) - + public init(rawValue: Int) { - self.rawValue = rawValue; + self.rawValue = rawValue } } // Define helpful typealiases -fileprivate typealias MLAnimationCompletionBlock = (_ finished: Bool) -> () +fileprivate typealias MLAnimationCompletionBlock = (_ finished: Bool) -> Void fileprivate typealias MLAnimation = (anim: CAKeyframeAnimation, duration: CGFloat) -fileprivate class GradientSetupAnimation: CABasicAnimation { +private class GradientSetupAnimation: CABasicAnimation { } fileprivate extension UIResponder { // Thanks to Phil M // http://stackoverflow.com/questions/1340434/get-to-uiviewcontroller-from-uiview-on-iphone - + func firstAvailableViewController() -> UIViewController? { // convenience function for casting and to "mask" the recursive function return self.traverseResponderChainForFirstViewController() } - + func traverseResponderChainForFirstViewController() -> UIViewController? { if let nextResponder = self.next { if nextResponder is UIViewController { @@ -1747,42 +1730,42 @@ fileprivate extension UIResponder { } fileprivate extension CAMediaTimingFunction { - + func durationPercentageForPositionPercentage(_ positionPercentage: CGFloat, duration: CGFloat) -> CGFloat { // Finds the animation duration percentage that corresponds with the given animation "position" percentage. // Utilizes Newton's Method to solve for the parametric Bezier curve that is used by CAMediaAnimation. - + let controlPoints = self.controlPoints() let epsilon: CGFloat = 1.0 / (100.0 * CGFloat(duration)) - + // Find the t value that gives the position percentage we want let t_found = solveTforY(positionPercentage, epsilon: epsilon, controlPoints: controlPoints) - + // With that t, find the corresponding animation percentage let durationPercentage = XforCurveAt(t_found, controlPoints: controlPoints) - + return durationPercentage } - + func solveTforY(_ y_0: CGFloat, epsilon: CGFloat, controlPoints: [CGPoint]) -> CGFloat { // Use Newton's Method: http://en.wikipedia.org/wiki/Newton's_method // For first guess, use t = y (i.e. if curve were linear) var t0 = y_0 var t1 = y_0 var f0, df0: CGFloat - + for _ in 0..<15 { // Base this iteration of t1 calculated from last iteration t0 = t1 // Calculate f(t0) - f0 = YforCurveAt(t0, controlPoints:controlPoints) - y_0 + f0 = YforCurveAt(t0, controlPoints: controlPoints) - y_0 // Check if this is close (enough) if (fabs(f0) < epsilon) { // Done! return t0 } // Else continue Newton's Method - df0 = derivativeCurveYValueAt(t0, controlPoints:controlPoints) + df0 = derivativeCurveYValueAt(t0, controlPoints: controlPoints) // Check if derivative is small or zero ( http://en.wikipedia.org/wiki/Newton's_method#Failure_analysis ) if (fabs(df0) < 1e-6) { break @@ -1790,56 +1773,56 @@ fileprivate extension CAMediaTimingFunction { // Else recalculate t1 t1 = t0 - f0/df0 } - + // Give up - shouldn't ever get here...I hope print("MarqueeLabel: Failed to find t for Y input!") return t0 } - - func YforCurveAt(_ t: CGFloat, controlPoints:[CGPoint]) -> CGFloat { + + func YforCurveAt(_ t: CGFloat, controlPoints: [CGPoint]) -> CGFloat { let P0 = controlPoints[0] let P1 = controlPoints[1] let P2 = controlPoints[2] let P3 = controlPoints[3] - + // Per http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves - let y0 = (pow((1.0 - t),3.0) * P0.y) + let y0 = (pow((1.0 - t), 3.0) * P0.y) let y1 = (3.0 * pow(1.0 - t, 2.0) * t * P1.y) let y2 = (3.0 * (1.0 - t) * pow(t, 2.0) * P2.y) let y3 = (pow(t, 3.0) * P3.y) - + return y0 + y1 + y2 + y3 } - + func XforCurveAt(_ t: CGFloat, controlPoints: [CGPoint]) -> CGFloat { let P0 = controlPoints[0] let P1 = controlPoints[1] let P2 = controlPoints[2] let P3 = controlPoints[3] - + // Per http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves - - let x0 = (pow((1.0 - t),3.0) * P0.x) + + let x0 = (pow((1.0 - t), 3.0) * P0.x) let x1 = (3.0 * pow(1.0 - t, 2.0) * t * P1.x) let x2 = (3.0 * (1.0 - t) * pow(t, 2.0) * P2.x) let x3 = (pow(t, 3.0) * P3.x) - + return x0 + x1 + x2 + x3 } - + func derivativeCurveYValueAt(_ t: CGFloat, controlPoints: [CGPoint]) -> CGFloat { let P0 = controlPoints[0] let P1 = controlPoints[1] let P2 = controlPoints[2] let P3 = controlPoints[3] - + let dy0 = (P0.y + 3.0 * P1.y + 3.0 * P2.y - P3.y) * -3.0 let dy1 = t * (6.0 * P0.y + 6.0 * P2.y) let dy2 = (-3.0 * P0.y + 3.0 * P1.y) return dy0 * pow(t, 2.0) + dy1 + dy2 } - + func controlPoints() -> [CGPoint] { // Create point array to point to var point: [Float] = [0.0, 0.0] @@ -1848,8 +1831,7 @@ fileprivate extension CAMediaTimingFunction { self.getControlPoint(at: i, values: &point) pointArray.append(CGPoint(x: CGFloat(point[0]), y: CGFloat(point[1]))) } - + return pointArray } } - diff --git a/Signal/src/views/RemoteVideoView.m b/Signal/src/views/RemoteVideoView.m index bd7faa95e..e6c211749 100644 --- a/Signal/src/views/RemoteVideoView.m +++ b/Signal/src/views/RemoteVideoView.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "RemoteVideoView.h" @@ -84,12 +84,9 @@ NS_ASSUME_NONNULL_BEGIN return self; } - // On iOS8: prints a message saying the feature is unavailable. - if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(9, 0)) { - _videoRenderer = [NullVideoRenderer new]; - [self addSubview:_videoRenderer]; - [_videoRenderer autoPinEdgesToSuperviewEdges]; - } + _videoRenderer = [NullVideoRenderer new]; + [self addSubview:_videoRenderer]; + [_videoRenderer autoPinEdgesToSuperviewEdges]; // Currently RTC only supports metal on 64bit machines #if defined(RTC_SUPPORTS_METAL) diff --git a/Signal/test/contact/ContactsPickerTest.swift b/Signal/test/contact/ContactsPickerTest.swift index 4fd677e43..21a86203e 100644 --- a/Signal/test/contact/ContactsPickerTest.swift +++ b/Signal/test/contact/ContactsPickerTest.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // import XCTest @@ -23,7 +23,6 @@ final class ContactsPickerTest: XCTestCase { } } - @available(iOS 9.0, *) func testContactSectionMatchesEmailFirstLetterWhenOnlyEmailContact() { setLangEN() @@ -48,7 +47,6 @@ final class ContactsPickerTest: XCTestCase { } } - @available(iOS 9.0, *) func testContactSectionMatchesNameFirstLetterWhenNameExistsInContact() { setLangEN() diff --git a/SignalMessaging/attachments/AttachmentApprovalViewController.swift b/SignalMessaging/attachments/AttachmentApprovalViewController.swift index 6ac4c68d3..3d2da6289 100644 --- a/SignalMessaging/attachments/AttachmentApprovalViewController.swift +++ b/SignalMessaging/attachments/AttachmentApprovalViewController.swift @@ -172,37 +172,35 @@ public class AttachmentApprovalViewController: OWSViewController, CaptioningTool // This allows us to zoom in on the media view without zooming in on the button if attachment.isVideo { - if #available(iOS 9.0, *) { - guard let videoURL = attachment.dataUrl else { - owsFail("Missing videoURL") - return - } + guard let videoURL = attachment.dataUrl else { + owsFail("Missing videoURL") + return + } - let player = OWSVideoPlayer(url: videoURL) - self.videoPlayer = player - player.delegate = self + let player = OWSVideoPlayer(url: videoURL) + self.videoPlayer = player + player.delegate = self - let playerView = VideoPlayerView() - playerView.player = player.avPlayer - self.mediaMessageView.addSubview(playerView) - playerView.autoPinEdgesToSuperviewEdges() + let playerView = VideoPlayerView() + playerView.player = player.avPlayer + self.mediaMessageView.addSubview(playerView) + playerView.autoPinEdgesToSuperviewEdges() - let pauseGesture = UITapGestureRecognizer(target: self, action: #selector(didTapPlayerView(_:))) - playerView.addGestureRecognizer(pauseGesture) + let pauseGesture = UITapGestureRecognizer(target: self, action: #selector(didTapPlayerView(_:))) + playerView.addGestureRecognizer(pauseGesture) - let progressBar = PlayerProgressBar() - progressBar.player = player.avPlayer - progressBar.delegate = self + let progressBar = PlayerProgressBar() + progressBar.player = player.avPlayer + progressBar.delegate = self - // we don't want the progress bar to zoom during "pinch-to-zoom" - // but we do want it to shrink with the media content when the user - // pops the keyboard. - contentContainer.addSubview(progressBar) + // we don't want the progress bar to zoom during "pinch-to-zoom" + // but we do want it to shrink with the media content when the user + // pops the keyboard. + contentContainer.addSubview(progressBar) - progressBar.autoPinEdge(.top, to: .bottom, of: topToolbar) - progressBar.autoPinWidthToSuperview() - progressBar.autoSetDimension(.height, toSize: 44) - } + progressBar.autoPinEdge(.top, to: .bottom, of: topToolbar) + progressBar.autoPinWidthToSuperview() + progressBar.autoSetDimension(.height, toSize: 44) self.mediaMessageView.videoPlayButton?.isHidden = true let playButton = UIButton() @@ -220,7 +218,6 @@ public class AttachmentApprovalViewController: OWSViewController, CaptioningTool } } - @available(iOS 9, *) public func didTapPlayerView(_ gestureRecognizer: UIGestureRecognizer) { assert(self.videoPlayer != nil) self.pauseVideo() @@ -279,44 +276,21 @@ public class AttachmentApprovalViewController: OWSViewController, CaptioningTool private func playVideo() { Logger.info("\(TAG) in \(#function)") - if #available(iOS 9, *) { - guard let videoPlayer = self.videoPlayer else { - owsFail("\(TAG) video player was unexpectedly nil") - return - } - - guard let playVideoButton = self.playVideoButton else { - owsFail("\(TAG) playVideoButton was unexpectedly nil") - return - } - UIView.animate(withDuration: 0.1) { - playVideoButton.alpha = 0.0 - } - videoPlayer.play() - } else { - self.playLegacyVideo() - } - } - - private func playLegacyVideo() { - if #available(iOS 9, *) { - owsFail("should only use legacy video on iOS8") - } - - guard let videoURL = self.attachment.dataUrl else { - owsFail("videoURL was unexpectedly nil") + guard let videoPlayer = self.videoPlayer else { + owsFail("\(TAG) video player was unexpectedly nil") return } - guard let playerVC = MPMoviePlayerViewController(contentURL: videoURL) else { - owsFail("failed to init legacy video player") + guard let playVideoButton = self.playVideoButton else { + owsFail("\(TAG) playVideoButton was unexpectedly nil") return } - - self.present(playerVC, animated: true) + UIView.animate(withDuration: 0.1) { + playVideoButton.alpha = 0.0 + } + videoPlayer.play() } - @available(iOS 9, *) private func pauseVideo() { guard let videoPlayer = self.videoPlayer else { owsFail("\(TAG) video player was unexpectedly nil") @@ -345,7 +319,6 @@ public class AttachmentApprovalViewController: OWSViewController, CaptioningTool } } - @available(iOS 9.0, *) public func playerProgressBarDidStartScrubbing(_ playerProgressBar: PlayerProgressBar) { // [self.videoPlayer pause]; guard let videoPlayer = self.videoPlayer else { @@ -355,7 +328,6 @@ public class AttachmentApprovalViewController: OWSViewController, CaptioningTool videoPlayer.pause() } - @available(iOS 9.0, *) public func playerProgressBar(_ playerProgressBar: PlayerProgressBar, scrubbedToTime time: CMTime) { guard let videoPlayer = self.videoPlayer else { owsFail("\(TAG) video player was unexpectedly nil") @@ -365,7 +337,6 @@ public class AttachmentApprovalViewController: OWSViewController, CaptioningTool videoPlayer.seek(to: time) } - @available(iOS 9.0, *) public func playerProgressBar(_ playerProgressBar: PlayerProgressBar, didFinishScrubbingAtTime time: CMTime, shouldResumePlayback: Bool) { guard let videoPlayer = self.videoPlayer else { owsFail("\(TAG) video player was unexpectedly nil") diff --git a/SignalMessaging/attachments/OWSVideoPlayer.swift b/SignalMessaging/attachments/OWSVideoPlayer.swift index e781a18db..f5405e64b 100644 --- a/SignalMessaging/attachments/OWSVideoPlayer.swift +++ b/SignalMessaging/attachments/OWSVideoPlayer.swift @@ -7,7 +7,6 @@ import AVFoundation @objc protocol OWSVideoPlayerDelegate: class { - @available(iOSApplicationExtension 9.0, *) func videoPlayerDidPlayToCompletion(_ videoPlayer: OWSVideoPlayer) } @@ -19,10 +18,9 @@ public class OWSVideoPlayer: NSObject { weak var delegate: OWSVideoPlayerDelegate? - @available(iOS 9.0, *) init(url: URL) { self.avPlayer = AVPlayer(url: url) - self.audioActivity = AudioActivity(audioDescription: "[OWSVideoPlayer] url:\(url)") + self.audioActivity = AudioActivity(audioDescription: "[OWSVideoPlayer] url:\(url)") super.init() @@ -34,13 +32,11 @@ public class OWSVideoPlayer: NSObject { // MARK: Playback Controls - @available(iOS 9.0, *) public func pause() { avPlayer.pause() OWSAudioSession.shared.endAudioActivity(self.audioActivity) } - @available(iOS 9.0, *) public func play() { OWSAudioSession.shared.setPlaybackCategory(audioActivity: self.audioActivity) @@ -57,7 +53,6 @@ public class OWSVideoPlayer: NSObject { avPlayer.play() } - @available(iOS 9.0, *) @objc(seekToTime:) public func seek(to time: CMTime) { avPlayer.seek(to: time) @@ -66,7 +61,6 @@ public class OWSVideoPlayer: NSObject { // MARK: private @objc - @available(iOS 9.0, *) private func playerItemDidPlayToCompletion(_ notification: Notification) { self.delegate?.videoPlayerDidPlayToCompletion(self) OWSAudioSession.shared.endAudioActivity(self.audioActivity) diff --git a/SignalMessaging/attachments/VideoPlayerView.swift b/SignalMessaging/attachments/VideoPlayerView.swift index 19070ba32..bef91c818 100644 --- a/SignalMessaging/attachments/VideoPlayerView.swift +++ b/SignalMessaging/attachments/VideoPlayerView.swift @@ -5,7 +5,6 @@ import Foundation import AVFoundation -@available(iOS 9.0, *) @objc public class VideoPlayerView: UIView { var player: AVPlayer? { @@ -27,7 +26,6 @@ public class VideoPlayerView: UIView { } } -@available(iOS 9.0, *) @objc public protocol PlayerProgressBarDelegate { func playerProgressBarDidStartScrubbing(_ playerProgressBar: PlayerProgressBar) @@ -35,7 +33,6 @@ public protocol PlayerProgressBarDelegate { func playerProgressBar(_ playerProgressBar: PlayerProgressBar, didFinishScrubbingAtTime time: CMTime, shouldResumePlayback: Bool) } -@available(iOS 9.0, *) @objc public class PlayerProgressBar: UIView { public let TAG = "[PlayerProgressBar]" @@ -150,7 +147,7 @@ public class PlayerProgressBar: UIView { @objc private func handleSliderTouchUp(_ slider: UISlider) { let sliderTime = time(slider: slider) - self.delegate?.playerProgressBar(self, didFinishScrubbingAtTime: sliderTime, shouldResumePlayback:wasPlayingWhenScrubbingStarted) + self.delegate?.playerProgressBar(self, didFinishScrubbingAtTime: sliderTime, shouldResumePlayback: wasPlayingWhenScrubbingStarted) } @objc diff --git a/SignalMessaging/categories/UIFont+OWS.m b/SignalMessaging/categories/UIFont+OWS.m index 5ee3a934d..1275111f8 100644 --- a/SignalMessaging/categories/UIFont+OWS.m +++ b/SignalMessaging/categories/UIFont+OWS.m @@ -10,38 +10,22 @@ NS_ASSUME_NONNULL_BEGIN + (UIFont *)ows_thinFontWithSize:(CGFloat)size { - if (@available(iOS 8.2, *)) { - return [UIFont systemFontOfSize:size weight:UIFontWeightThin]; - } else { - return [UIFont fontWithName:@"HelveticaNeue-Thin" size:size]; - } + return [UIFont systemFontOfSize:size weight:UIFontWeightThin]; } + (UIFont *)ows_lightFontWithSize:(CGFloat)size { - if (@available(iOS 8.2, *)) { - return [UIFont systemFontOfSize:size weight:UIFontWeightLight]; - } else { - return [UIFont fontWithName:@"HelveticaNeue-Light" size:size]; - } + return [UIFont systemFontOfSize:size weight:UIFontWeightLight]; } + (UIFont *)ows_regularFontWithSize:(CGFloat)size { - if (@available(iOS 8.2, *)) { - return [UIFont systemFontOfSize:size weight:UIFontWeightRegular]; - } else { - return [UIFont fontWithName:@"HelveticaNeue" size:size]; - } + return [UIFont systemFontOfSize:size weight:UIFontWeightRegular]; } + (UIFont *)ows_mediumFontWithSize:(CGFloat)size { - if (@available(iOS 8.2, *)) { - return [UIFont systemFontOfSize:size weight:UIFontWeightMedium]; - } else { - return [UIFont fontWithName:@"HelveticaNeue-Medium" size:size]; - } + return [UIFont systemFontOfSize:size weight:UIFontWeightMedium]; } + (UIFont *)ows_boldFontWithSize:(CGFloat)size @@ -85,25 +69,12 @@ NS_ASSUME_NONNULL_BEGIN + (UIFont *)ows_dynamicTypeTitle2Font { - if (@available(iOS 9.0, *)) { - return [UIFont preferredFontForTextStyle:UIFontTextStyleTitle2]; - } else { - // Dynamic title font for ios8 defaults to bold 12.0 pt, whereas ios9+ it's 22.0pt regular weight. - // Here we chose to break dynamic font, in order to have uniform style across versions. - // It's already huge, so it's unlikely to present a usability issue. - // Handy font translations: http://swiftiostutorials.com/comparison-of-system-fonts-on-ios-8-and-ios-9/ - return [self ows_regularFontWithSize:22.0]; - } + return [UIFont preferredFontForTextStyle:UIFontTextStyleTitle2]; } + (UIFont *)ows_dynamicTypeHeadlineFont { - if (@available(iOS 9.0, *)) { - return [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; - } else { - // See ows_dynamicTypeTitle2Font. - return [self ows_regularFontWithSize:17.0]; - } + return [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; } @end diff --git a/SignalMessaging/categories/UIView+OWS.m b/SignalMessaging/categories/UIView+OWS.m index 9512b96f3..2297497ff 100644 --- a/SignalMessaging/categories/UIView+OWS.m +++ b/SignalMessaging/categories/UIView+OWS.m @@ -253,12 +253,8 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) - (BOOL)isRTL { - if (@available(iOS 9.0, *)) { - return ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute] - == UIUserInterfaceLayoutDirectionRightToLeft); - } else { - return [CurrentAppContext() isRTL]; - } + return ([UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute] + == UIUserInterfaceLayoutDirectionRightToLeft); } - (NSLayoutConstraint *)autoPinLeadingToSuperview @@ -270,16 +266,10 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) { self.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(iOS 9.0, *)) { - NSLayoutConstraint *constraint = - [self.leadingAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.leadingAnchor - constant:margin]; - constraint.active = YES; - return constraint; - } else { - margin += (self.isRTL ? self.superview.layoutMargins.right : self.superview.layoutMargins.left); - return [self autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:margin]; - } + NSLayoutConstraint *constraint = + [self.leadingAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.leadingAnchor constant:margin]; + constraint.active = YES; + return constraint; } - (NSLayoutConstraint *)autoPinTrailingToSuperview @@ -291,16 +281,10 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) { self.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(iOS 9.0, *)) { - NSLayoutConstraint *constraint = - [self.trailingAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.trailingAnchor - constant:-margin]; - constraint.active = YES; - return constraint; - } else { - margin += (self.isRTL ? self.superview.layoutMargins.left : self.superview.layoutMargins.right); - return [self autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:margin]; - } + NSLayoutConstraint *constraint = + [self.trailingAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.trailingAnchor constant:-margin]; + constraint.active = YES; + return constraint; } - (NSLayoutConstraint *)autoPinBottomToSuperview @@ -312,14 +296,10 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) { self.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(iOS 9.0, *)) { - NSLayoutConstraint *constraint = - [self.bottomAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.bottomAnchor constant:-margin]; - constraint.active = YES; - return constraint; - } else { - return [self autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:margin]; - } + NSLayoutConstraint *constraint = + [self.bottomAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.bottomAnchor constant:-margin]; + constraint.active = YES; + return constraint; } - (NSLayoutConstraint *)autoPinTopToSuperview @@ -331,14 +311,10 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) { self.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(iOS 9.0, *)) { - NSLayoutConstraint *constraint = - [self.topAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.topAnchor constant:margin]; - constraint.active = YES; - return constraint; - } else { - return [self autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:margin]; - } + NSLayoutConstraint *constraint = + [self.topAnchor constraintEqualToAnchor:self.superview.layoutMarginsGuide.topAnchor constant:margin]; + constraint.active = YES; + return constraint; } - (NSLayoutConstraint *)autoPinLeadingToTrailingOfView:(UIView *)view @@ -354,14 +330,9 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) self.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(iOS 9.0, *)) { - NSLayoutConstraint *constraint = - [self.leadingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margin]; - constraint.active = YES; - return constraint; - } else { - return [self autoPinEdge:ALEdgeLeading toEdge:ALEdgeTrailing ofView:view withOffset:margin]; - } + NSLayoutConstraint *constraint = [self.leadingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margin]; + constraint.active = YES; + return constraint; } - (NSLayoutConstraint *)autoPinTrailingToLeadingOfView:(UIView *)view @@ -377,14 +348,9 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) self.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(iOS 9.0, *)) { - NSLayoutConstraint *constraint = - [self.trailingAnchor constraintEqualToAnchor:view.leadingAnchor constant:-margin]; - constraint.active = YES; - return constraint; - } else { - return [self autoPinEdge:ALEdgeTrailing toEdge:ALEdgeLeading ofView:view withOffset:margin]; - } + NSLayoutConstraint *constraint = [self.trailingAnchor constraintEqualToAnchor:view.leadingAnchor constant:-margin]; + constraint.active = YES; + return constraint; } - (NSLayoutConstraint *)autoPinLeadingToView:(UIView *)view @@ -400,14 +366,9 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) self.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(iOS 9.0, *)) { - NSLayoutConstraint *constraint = - [self.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:margin]; - constraint.active = YES; - return constraint; - } else { - return [self autoPinEdge:ALEdgeLeading toEdge:ALEdgeLeading ofView:view withOffset:margin]; - } + NSLayoutConstraint *constraint = [self.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:margin]; + constraint.active = YES; + return constraint; } - (NSLayoutConstraint *)autoPinTrailingToView:(UIView *)view @@ -423,14 +384,9 @@ CGFloat ScaleFromIPhone5(CGFloat iPhone5Value) self.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(iOS 9.0, *)) { - NSLayoutConstraint *constraint = - [self.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margin]; - constraint.active = YES; - return constraint; - } else { - return [self autoPinEdge:ALEdgeTrailing toEdge:ALEdgeTrailing ofView:view withOffset:margin]; - } + NSLayoutConstraint *constraint = [self.trailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margin]; + constraint.active = YES; + return constraint; } - (NSTextAlignment)textAlignmentUnnatural diff --git a/SignalMessaging/contacts/SystemContactsFetcher.swift b/SignalMessaging/contacts/SystemContactsFetcher.swift index f58bee2f9..4c6246899 100644 --- a/SignalMessaging/contacts/SystemContactsFetcher.swift +++ b/SignalMessaging/contacts/SystemContactsFetcher.swift @@ -20,7 +20,6 @@ protocol ContactStoreAdaptee { func startObservingChanges(changeHandler: @escaping () -> Void) } -@available(iOS 9.0, *) class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee { let TAG = "[ContactsFrameworkContactStoreAdaptee]" private let contactStore = CNContactStore() @@ -107,177 +106,6 @@ class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee { } } -let kAddressBookContactStoreDidChangeNotificationName = NSNotification.Name("AddressBookContactStoreAdapteeDidChange") -/** - * System contact fetching compatible with iOS8 - */ -class AddressBookContactStoreAdaptee: ContactStoreAdaptee { - - let TAG = "[AddressBookContactStoreAdaptee]" - - private var addressBook: ABAddressBook = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue() - private var changeHandler: (() -> Void)? - let supportsContactEditing = false - - var authorizationStatus: ContactStoreAuthorizationStatus { - switch ABAddressBookGetAuthorizationStatus() { - case .notDetermined: - return .notDetermined - case .restricted: - return .restricted - case .denied: - return .denied - case .authorized: - return .authorized - } - } - - @objc - func runChangeHandler() { - guard let changeHandler = self.changeHandler else { - owsFail("\(TAG) trying to run change handler before it was registered") - return - } - changeHandler() - } - - func startObservingChanges(changeHandler: @escaping () -> Void) { - // should only call once - assert(self.changeHandler == nil) - self.changeHandler = changeHandler - - NotificationCenter.default.addObserver(self, selector: #selector(runChangeHandler), name: kAddressBookContactStoreDidChangeNotificationName, object: nil) - - let callback: ABExternalChangeCallback = { (_, _, _) in - // Ideally we'd just call the changeHandler here, but because this is a C style callback in swift, - // we can't capture any state in the closure, so we use a notification as a trampoline - NotificationCenter.default.postNotificationNameAsync(kAddressBookContactStoreDidChangeNotificationName, object: nil) - } - - ABAddressBookRegisterExternalChangeCallback(addressBook, callback, nil) - } - - func requestAccess(completionHandler: @escaping (Bool, Error?) -> Void) { - ABAddressBookRequestAccessWithCompletion(addressBook, completionHandler) - } - - func fetchContacts() -> Result<[Contact], Error> { - // Changes are not reflected unless we create a new address book - self.addressBook = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue() - - let allPeople = ABAddressBookCopyArrayOfAllPeopleInSourceWithSortOrdering(addressBook, nil, ABPersonGetSortOrdering()).takeRetainedValue() as [ABRecord] - - let contacts = allPeople.map { self.buildContact(abRecord: $0) } - - return .success(contacts) - } - - private func buildContact(abRecord: ABRecord) -> Contact { - - let addressBookRecord = OWSABRecord(abRecord: abRecord) - - var firstName = addressBookRecord.firstName - let lastName = addressBookRecord.lastName - let phoneNumbers = addressBookRecord.phoneNumbers - - if firstName == nil && lastName == nil { - if let companyName = addressBookRecord.companyName { - firstName = companyName - } else { - firstName = phoneNumbers.first - } - } - - return Contact(firstName: firstName, - lastName: lastName, - userTextPhoneNumbers: phoneNumbers, - imageData: addressBookRecord.imageData, - contactID: addressBookRecord.recordId) - } -} - -/** - * Wrapper around ABRecord for easy property extraction. - * Some code lifted from: - * https://github.com/SocialbitGmbH/SwiftAddressBook/blob/c1993fa/Pod/Classes/SwiftAddressBookPerson.swift - */ -struct OWSABRecord { - - public struct MultivalueEntry { - public var value: T - public var label: String? - public let id: Int - - public init(value: T, label: String?, id: Int) { - self.value = value - self.label = label - self.id = id - } - } - - let abRecord: ABRecord - - init(abRecord: ABRecord) { - self.abRecord = abRecord - } - - var firstName: String? { - return self.extractProperty(kABPersonFirstNameProperty) - } - - var lastName: String? { - return self.extractProperty(kABPersonLastNameProperty) - } - - var companyName: String? { - return self.extractProperty(kABPersonOrganizationProperty) - } - - var recordId: ABRecordID { - return ABRecordGetRecordID(abRecord) - } - - // We don't yet support labels for our iOS8 users. - var phoneNumbers: [String] { - if let result: [MultivalueEntry] = extractMultivalueProperty(kABPersonPhoneProperty) { - return result.map { $0.value } - } else { - return [] - } - } - - var imageData: Data? { - guard ABPersonHasImageData(abRecord) else { - return nil - } - guard let data = ABPersonCopyImageData(abRecord)?.takeRetainedValue() else { - return nil - } - return data as Data - } - - private func extractProperty(_ propertyName: ABPropertyID) -> T? { - let value: AnyObject? = ABRecordCopyValue(self.abRecord, propertyName)?.takeRetainedValue() - return value as? T - } - - fileprivate func extractMultivalueProperty(_ propertyName: ABPropertyID) -> Array>? { - guard let multivalue: ABMultiValue = extractProperty(propertyName) else { return nil } - var array = Array>() - for i: Int in 0..<(ABMultiValueGetCount(multivalue)) { - let value: T? = ABMultiValueCopyValueAtIndex(multivalue, i).takeRetainedValue() as? T - if let v: T = value { - let id: Int = Int(ABMultiValueGetIdentifierAtIndex(multivalue, i)) - let optionalLabel = ABMultiValueCopyLabelAtIndex(multivalue, i)?.takeRetainedValue() - array.append(MultivalueEntry(value: v, - label: optionalLabel == nil ? nil : optionalLabel! as String, - id: id)) - } - } - return !array.isEmpty ? array : nil - } -} - public enum ContactStoreAuthorizationStatus { case notDetermined, restricted, @@ -290,11 +118,7 @@ class ContactStoreAdapter: ContactStoreAdaptee { let adaptee: ContactStoreAdaptee init() { - if #available(iOS 9.0, *) { - self.adaptee = ContactsFrameworkContactStoreAdaptee() - } else { - self.adaptee = AddressBookContactStoreAdaptee() - } + self.adaptee = ContactsFrameworkContactStoreAdaptee() } var supportsContactEditing: Bool { diff --git a/SignalMessaging/views/OWSAlerts.swift b/SignalMessaging/views/OWSAlerts.swift index e66616791..5b39c59d6 100644 --- a/SignalMessaging/views/OWSAlerts.swift +++ b/SignalMessaging/views/OWSAlerts.swift @@ -10,8 +10,8 @@ import Foundation /// Cleanup and present alert for no permissions @objc public class func showNoMicrophonePermissionAlert() { - let alertTitle = NSLocalizedString("CALL_AUDIO_PERMISSION_TITLE", comment:"Alert title when calling and permissions for microphone are missing") - let alertMessage = NSLocalizedString("CALL_AUDIO_PERMISSION_MESSAGE", comment:"Alert message when calling and permissions for microphone are missing") + let alertTitle = NSLocalizedString("CALL_AUDIO_PERMISSION_TITLE", comment: "Alert title when calling and permissions for microphone are missing") + let alertMessage = NSLocalizedString("CALL_AUDIO_PERMISSION_MESSAGE", comment: "Alert message when calling and permissions for microphone are missing") let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert) let dismissAction = UIAlertAction(title: CommonStrings.dismissButton, style: .cancel) @@ -68,6 +68,8 @@ import Foundation @objc public class func showIOSUpgradeNagIfNecessary() { // Only show the nag to iOS 8 users. + // + // NOTE: Our current minimum iOS version is 9, so this should never show. if #available(iOS 9.0, *) { return } @@ -88,9 +90,9 @@ import Foundation Environment.preferences().setIOSUpgradeNagDate(Date()) - OWSAlerts.showAlert(withTitle:NSLocalizedString("UPGRADE_IOS_ALERT_TITLE", - comment:"Title for the alert indicating that user should upgrade iOS."), - message:NSLocalizedString("UPGRADE_IOS_ALERT_MESSAGE", - comment:"Message for the alert indicating that user should upgrade iOS.")) + OWSAlerts.showAlert(withTitle: NSLocalizedString("UPGRADE_IOS_ALERT_TITLE", + comment: "Title for the alert indicating that user should upgrade iOS."), + message: NSLocalizedString("UPGRADE_IOS_ALERT_MESSAGE", + comment: "Message for the alert indicating that user should upgrade iOS.")) } } From 2c1560692322e6c4bebcf4f6887eadee3d97e910 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 26 Feb 2018 14:14:19 -0500 Subject: [PATCH 4/4] Respond to CR. --- Signal/src/views/MarqueeLabel.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Signal/src/views/MarqueeLabel.swift b/Signal/src/views/MarqueeLabel.swift index 46df0bdbb..32e9a9e1a 100644 --- a/Signal/src/views/MarqueeLabel.swift +++ b/Signal/src/views/MarqueeLabel.swift @@ -1,5 +1,10 @@ +// Grabbed from: https://github.com/cbpowell/MarqueeLabel-Swift/blob/cd331f3cfc3f9d7114ffa5aa4f243f1d5eda9d0d/Classes/MarqueeLabel.swift +// License: MIT License // -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// MarqueeLabel.swift +// +// Created by Charles Powell on 8/6/14. +// Copyright (c) 2015 Charles Powell. All rights reserved. // import UIKit