From 706c408fdef40b8bc8dca2b34c9aa5357b7d6713 Mon Sep 17 00:00:00 2001 From: Apple <> Date: Wed, 18 Aug 2021 06:30:51 -0800 Subject: [PATCH] Upgrade to iOS 15, added button configurations, UIVisualEffectView, UIToolTipInteraction, Pointer style buttons, SF Symbols --- LICENSE/LICENSE.txt | 2 +- README.md | 184 +++--- UIKitCatalog.xcodeproj/project.pbxproj | 224 ++++++- .../ActivityIndicatorViewController.swift | 115 ++-- .../AlertControllerViewController.swift | 83 +-- UIKitCatalog/AppDelegate.swift | 1 - .../Contents.json | 0 .../stepper_and_segment_background_1x.png | Bin .../stepper_and_segment_background_2x.png | Bin .../stepper_and_segment_background_3x.png | Bin .../Contents.json | 0 ...per_and_segment_background_disabled_1x.png | Bin ...per_and_segment_background_disabled_2x.png | Bin ...per_and_segment_background_disabled_3x.png | Bin .../Contents.json | 0 ..._and_segment_background_highlighted_1x.png | Bin ..._and_segment_background_highlighted_2x.png | Bin ..._and_segment_background_highlighted_3x.png | Bin .../Contents.json | 12 +- .../stepper_and_segment_divider_3x.png | Bin ...stepper_and_segment_segment_divider_1x.png | Bin ...stepper_and_segment_segment_divider_2x.png | Bin .../stepper_decrement.imageset/Contents.json | 23 - .../decrement_1x.png | Bin 1068 -> 0 bytes .../decrement_2x.png | Bin 1123 -> 0 bytes .../stepper_decrement_3x.png | Bin 2917 -> 0 bytes .../stepper_increment.imageset/Contents.json | 23 - .../increment_1x.png | Bin 1166 -> 0 bytes .../increment_2x.png | Bin 1244 -> 0 bytes .../stepper_increment_3x.png | Bin 3063 -> 0 bytes .../x_icon.imageset/Contents.json | 23 - .../x_icon.imageset/x_icon_1x.png | Bin 1424 -> 0 bytes .../x_icon.imageset/x_icon_2x.png | Bin 1746 -> 0 bytes .../x_icon.imageset/x_icon_3x.png | Bin 3419 -> 0 bytes ...ActivityIndicatorViewController.storyboard | 173 ++--- .../ButtonViewController.storyboard | 603 ++++++++++++------ UIKitCatalog/Base.lproj/Credits.rtf | 5 +- .../Base.lproj/ImageViewController.storyboard | 13 +- UIKitCatalog/Base.lproj/InfoPlist.strings | 4 +- UIKitCatalog/Base.lproj/Localizable.strings | 96 ++- UIKitCatalog/Base.lproj/Main.storyboard | 8 +- .../MenuButtonViewController.storyboard | 112 ++++ ...InteractionButtonViewController.storyboard | 165 +++++ .../ProgressViewController.storyboard | 158 +++-- .../SegmentedControlViewController.storyboard | 274 ++++---- .../SliderViewController.storyboard | 194 +++--- .../StepperViewController.storyboard | 186 +++--- .../SwitchViewController.storyboard | 138 ++-- .../SymbolViewController.storyboard | 166 +++++ .../TextFieldViewController.storyboard | 307 ++++----- .../VisualEffectViewController.storyboard | 53 ++ UIKitCatalog/Base.lproj/content.html | 3 +- UIKitCatalog/BaseTableViewController.swift | 52 ++ .../ButtonViewController+Configs.swift | 468 ++++++++++++++ UIKitCatalog/ButtonViewController.swift | 256 ++++---- UIKitCatalog/CaseElement.swift | 29 + UIKitCatalog/ColorPickerViewController.swift | 65 +- .../CustomPageControlViewController.swift | 2 +- .../CustomSearchBarViewController.swift | 6 +- .../CustomToolbarViewController.swift | 4 +- UIKitCatalog/DatePickerController.swift | 6 + .../DefaultPageControlViewController.swift | 2 +- .../DefaultSearchBarViewController.swift | 6 +- .../DefaultToolbarViewController.swift | 2 +- UIKitCatalog/FontPickerViewController.swift | 4 +- UIKitCatalog/ImageViewController.swift | 7 + UIKitCatalog/MenuButtonViewController.swift | 184 ++++++ UIKitCatalog/OutlineViewController.swift | 137 ++-- ...interInteractionButtonViewController.swift | 168 +++++ UIKitCatalog/ProgressViewController.swift | 132 ++-- UIKitCatalog/SceneDelegate.swift | 74 ++- .../SegmentedControlViewController.swift | 169 ++--- UIKitCatalog/SliderViewController.swift | 167 +++-- UIKitCatalog/StackViewController.swift | 4 - UIKitCatalog/StepperViewController.swift | 123 ++-- UIKitCatalog/SwitchViewController.swift | 125 ++-- UIKitCatalog/SymbolViewController.swift | 103 +++ UIKitCatalog/TextFieldViewController.swift | 180 +++--- UIKitCatalog/TextViewController.swift | 23 +- UIKitCatalog/UIKitCatalog-Info.plist | 46 +- .../Base.lproj/Localizable.stringsdict | 41 ++ UIKitCatalog/VisualEffectViewController.swift | 68 ++ 82 files changed, 4031 insertions(+), 1970 deletions(-) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_background.imageset => background.imageset}/Contents.json (100%) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_background.imageset => background.imageset}/stepper_and_segment_background_1x.png (100%) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_background.imageset => background.imageset}/stepper_and_segment_background_2x.png (100%) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_background.imageset => background.imageset}/stepper_and_segment_background_3x.png (100%) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_background_disabled.imageset => background_disabled.imageset}/Contents.json (100%) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_background_disabled.imageset => background_disabled.imageset}/stepper_and_segment_background_disabled_1x.png (100%) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_background_disabled.imageset => background_disabled.imageset}/stepper_and_segment_background_disabled_2x.png (100%) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_background_disabled.imageset => background_disabled.imageset}/stepper_and_segment_background_disabled_3x.png (100%) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_background_highlighted.imageset => background_highlighted.imageset}/Contents.json (100%) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_background_highlighted.imageset => background_highlighted.imageset}/stepper_and_segment_background_highlighted_1x.png (100%) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_background_highlighted.imageset => background_highlighted.imageset}/stepper_and_segment_background_highlighted_2x.png (100%) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_background_highlighted.imageset => background_highlighted.imageset}/stepper_and_segment_background_highlighted_3x.png (100%) rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_segment_divider.imageset => stepper_and_segment_divider.imageset}/Contents.json (90%) mode change 100755 => 100644 rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_segment_divider.imageset => stepper_and_segment_divider.imageset}/stepper_and_segment_divider_3x.png (100%) mode change 100755 => 100644 rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_segment_divider.imageset => stepper_and_segment_divider.imageset}/stepper_and_segment_segment_divider_1x.png (100%) mode change 100755 => 100644 rename UIKitCatalog/Assets.xcassets/{stepper_and_segment_segment_divider.imageset => stepper_and_segment_divider.imageset}/stepper_and_segment_segment_divider_2x.png (100%) mode change 100755 => 100644 delete mode 100755 UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/Contents.json delete mode 100755 UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/decrement_1x.png delete mode 100755 UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/decrement_2x.png delete mode 100755 UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/stepper_decrement_3x.png delete mode 100755 UIKitCatalog/Assets.xcassets/stepper_increment.imageset/Contents.json delete mode 100755 UIKitCatalog/Assets.xcassets/stepper_increment.imageset/increment_1x.png delete mode 100755 UIKitCatalog/Assets.xcassets/stepper_increment.imageset/increment_2x.png delete mode 100755 UIKitCatalog/Assets.xcassets/stepper_increment.imageset/stepper_increment_3x.png delete mode 100755 UIKitCatalog/Assets.xcassets/x_icon.imageset/Contents.json delete mode 100755 UIKitCatalog/Assets.xcassets/x_icon.imageset/x_icon_1x.png delete mode 100755 UIKitCatalog/Assets.xcassets/x_icon.imageset/x_icon_2x.png delete mode 100755 UIKitCatalog/Assets.xcassets/x_icon.imageset/x_icon_3x.png create mode 100755 UIKitCatalog/Base.lproj/MenuButtonViewController.storyboard create mode 100755 UIKitCatalog/Base.lproj/PointerInteractionButtonViewController.storyboard create mode 100755 UIKitCatalog/Base.lproj/SymbolViewController.storyboard create mode 100755 UIKitCatalog/Base.lproj/VisualEffectViewController.storyboard create mode 100644 UIKitCatalog/BaseTableViewController.swift create mode 100755 UIKitCatalog/ButtonViewController+Configs.swift create mode 100644 UIKitCatalog/CaseElement.swift create mode 100755 UIKitCatalog/MenuButtonViewController.swift create mode 100755 UIKitCatalog/PointerInteractionButtonViewController.swift create mode 100755 UIKitCatalog/SymbolViewController.swift create mode 100644 UIKitCatalog/UIKitCatalog/Base.lproj/Localizable.stringsdict create mode 100755 UIKitCatalog/VisualEffectViewController.swift diff --git a/LICENSE/LICENSE.txt b/LICENSE/LICENSE.txt index 5e871b1..1f0d057 100755 --- a/LICENSE/LICENSE.txt +++ b/LICENSE/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright © 2020 Apple Inc. +Copyright © 2021 Apple Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index b9772f1..dfed021 100755 --- a/README.md +++ b/README.md @@ -4,80 +4,75 @@ Customize your app's user interface with views and controls. ## Overview -This sample guides you through several types of customizations you can make in your iOS app. The sample uses a split-view controller architecture for navigating UIKit views and controls. The primary view controller (`MasterViewController`) shows the available views and controls. When you select one, `MasterViewController` shows the secondary view controller associated with it. +This sample guides you through several types of customizations you can make in your iOS app. It is built with Mac Catalyst, which means the sample runs on both iOS and macOS. The sample uses a split-view controller architecture to navigate UIKit views and controls. The primary view controller (`OutlineViewController`) shows the available views and controls. When you select one, `OutlineViewController` shows the secondary view controller associated with it. -The name of each secondary view controller reflects its *target item*. For example, the `AlertControllerViewController` class shows how to use a `UIAlertController` object. The only exceptions to this rule are `UISearchBar` and `UIToolbar`; the sample demonstrates these APIs in multiple view controllers to explain how their controls function and how to customize them. To demonstrate how to manage the complexity of your storyboards, the app hosts all view controllers in a separate storyboard and loaded when needed. +The name of each secondary view controller reflects its target item. For example, the `AlertControllerViewController` class shows how to use a `UIAlertController` object. The only exceptions to this rule are `UISearchBar` and `UIToolbar`; the sample demonstrates these APIs in multiple view controllers to explain how their controls function and how to customize them. To demonstrate how to manage the complexity of your storyboards, the app hosts all view controllers in a separate storyboard and loads each as it's when needed. -This sample demonstrates the following views and controls (several of which are referenced in the sections below): +This sample demonstrates the following views and controls; several of which are referenced in the sections below: -* [`UIActivityIndicatorView`](https://developer.apple.com/documentation/uikit/uiactivityindicatorview) -* [`UIAlertController`](https://developer.apple.com/documentation/uikit/uialertcontroller) -* [`UIButton`](https://developer.apple.com/documentation/uikit/uibutton) -* [`UIDatePicker`](https://developer.apple.com/documentation/uikit/uidatepicker) -* [`UIPickerView`](https://developer.apple.com/documentation/uikit/uipickerview) -* [`UIColorPickerViewController`](https://developer.apple.com/documentation/uikit/uicolorpickerviewcontroller) -* [`UIColorWell`](https://developer.apple.com/documentation/uikit/uicolorwell) -* [`UIFontPickerViewController`](https://developer.apple.com/documentation/uikit/uifontpickerviewcontroller) -* [`UIImagePickerViewController`](https://developer.apple.com/documentation/uikit/uiimagepickercontroller) -* [`UIImageView`](https://developer.apple.com/documentation/uikit/uiimageview) -* [`UIPageControl`](https://developer.apple.com/documentation/uikit/uipagecontrol) -* [`UIProgressView`](https://developer.apple.com/documentation/uikit/uiprogressview) -* [`UISearchBar`](https://developer.apple.com/documentation/uikit/uisearchbar) -* [`UISegmentedControl`](https://developer.apple.com/documentation/uikit/uisegmentedcontrol) -* [`UISlider`](https://developer.apple.com/documentation/uikit/uislider) -* [`UIStackView`](https://developer.apple.com/documentation/uikit/uistackview) -* [`UIStepper`](https://developer.apple.com/documentation/uikit/uistepper) -* [`UISwitch`](https://developer.apple.com/documentation/uikit/uiswitch) -* [`UITextField`](https://developer.apple.com/documentation/uikit/uitextfield) -* [`UITextFormattingCoordinator`](https://developer.apple.com/documentation/uikit/uitextformattingcoordinator) -* [`UITextView`](https://developer.apple.com/documentation/uikit/uitextview) -* [`UIToolbar`](https://developer.apple.com/documentation/uikit/uitoolbar) -* [`WKWebView`](https://developer.apple.com/documentation/webkit/wkwebview) +* [UIActivityIndicatorView](https://developer.apple.com/documentation/uikit/uiactivityindicatorview) +* [UIAlertController](https://developer.apple.com/documentation/uikit/uialertcontroller) +* [UIButton](https://developer.apple.com/documentation/uikit/uibutton) +* [UIButton - PointerStyleProvider](https://developer.apple.com/documentation/uikit/uibutton/pointerstyleprovider) +* [UIDatePicker](https://developer.apple.com/documentation/uikit/uidatepicker) +* [UIPickerView](https://developer.apple.com/documentation/uikit/uipickerview) +* [UIColorPickerViewController](https://developer.apple.com/documentation/uikit/uicolorpickerviewcontroller) +* [UIColorWell](https://developer.apple.com/documentation/uikit/uicolorwell) +* [UIFontPickerViewController](https://developer.apple.com/documentation/uikit/uifontpickerviewcontroller) +* [UIImagePickerViewController](https://developer.apple.com/documentation/uikit/uiimagepickercontroller) +* [UIImageView](https://developer.apple.com/documentation/uikit/uiimageview) +* [UIImageView with SF Symbols](https://developer.apple.com/design/human-interface-guidelines/sf-symbols/overview) +* [UIPageControl](https://developer.apple.com/documentation/uikit/uipagecontrol) +* [UIProgressView](https://developer.apple.com/documentation/uikit/uiprogressview) +* [UISearchBar](https://developer.apple.com/documentation/uikit/uisearchbar) +* [UISegmentedControl](https://developer.apple.com/documentation/uikit/uisegmentedcontrol) +* [UISlider](https://developer.apple.com/documentation/uikit/uislider) +* [UIStackView](https://developer.apple.com/documentation/uikit/uistackview) +* [UIStepper](https://developer.apple.com/documentation/uikit/uistepper) +* [UISwitch](https://developer.apple.com/documentation/uikit/uiswitch) +* [UITextField](https://developer.apple.com/documentation/uikit/uitextfield) +* [UITextFormattingCoordinator](https://developer.apple.com/documentation/uikit/uitextformattingcoordinator) +* [UITextView](https://developer.apple.com/documentation/uikit/uitextview) +* [UIToolbar](https://developer.apple.com/documentation/uikit/uitoolbar) +* [UIVisualEffectView](https://developer.apple.com/documentation/uikit/uivisualeffect) +* [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview) -## Add Accessibility Support to Your Views +## Configure the Sample Code Project -VoiceOver and other system accessibility technologies use the information provided by views and controls to help all users navigate content. UIKit views include default accessibility support. Improve user experience by providing custom accessibility information. +In Xcode, select your development team on the iOS-Mac target's Signing and Capabilities tab. -In this UIKitCatalog sample, several view controllers configure the `accessibilityType` and `accessibilityLabel` properties of their associated view. Picker view columns don't have labels, so the picker view asks its delegate for the corresponding accessibility information: +## Customize the Look of Buttons with Button Configurations + +You can customize the appearance and behavior of a UIButton by using `UIButton.Configuration`. This sample uses a `filled()` configuration so that the button draws with a red background color: ``` swift -func pickerView(_ pickerView: UIPickerView, accessibilityLabelForComponent component: Int) -> String? { - - switch ColorComponent(rawValue: component)! { - case .red: - return NSLocalizedString("Red color component value", comment: "") - - case .green: - return NSLocalizedString("Green color component value", comment: "") - - case .blue: - return NSLocalizedString("Blue color component value", comment: "") - } -} +var config = UIButton.Configuration.filled() +config.background.backgroundColor = .systemRed +button.configuration = config ``` ## Display a Custom Alert -`AlertControllerViewController` demonstrates several techniques for displaying modal alerts and action sheets from an interface. The configuration process is similar for all alerts: +`AlertControllerViewController` demonstrates several techniques to display modal alerts and action sheets from an interface. The configuration process is similar for all alerts: -1. Determine the message you want to display in the alert. +1. Determine the message to display in the alert. 2. Create and configure a `UIAlertController` object. 3. Add handlers for actions the user may take. 4. Present the alert controller. -The `showSimpleAlert` function uses the `NSLocalizedString` function to retrieve the alert messages in the user’s preferred language. The `showSimpleAlert` function uses those strings to create and configure the `UIAlertController` object. Although the button in the alert has the title OK, the sample uses a cancel action to ensure the alert controller applies the proper styling to the button: +The `showSimpleAlert` function uses the `NSLocalizedString` function to retrieve the alert messages in the user’s preferred language. The `showSimpleAlert` function uses those strings to create and configure the `UIAlertController` object. Although the button in the alert has the title OK, the sample uses a cancel action to ensure that the alert controller applies the proper styling to the button: ``` swift func showSimpleAlert() { let title = NSLocalizedString("A Short Title is Best", comment: "") - let message = NSLocalizedString("A message should be a short, complete sentence.", comment: "") + let message = NSLocalizedString("A message needs to be a short, complete sentence.", comment: "") let cancelButtonTitle = NSLocalizedString("OK", comment: "") let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) // Create the action. let cancelAction = UIAlertAction(title: cancelButtonTitle, style: .cancel) { _ in - print("The simple alert's cancel action occurred.") + Swift.debugPrint("The simple alert's cancel action occurred.") } // Add the action. @@ -89,31 +84,50 @@ func showSimpleAlert() { ## Customize the Appearance of Sliders -This sample demonstrates different ways to display a `UISlider`, a control you use to select a single value from a continuous range of values. Customize the appearance of a slider by assigning stretchable images for left-side tracking, right-side tracking, and the thumb. In this example, the track image is stretchable and is one pixel wide. Make the track images wider to provide rounded corners, but be sure to set these images' `capInsets` property to allow for the corners. +This sample demonstrates different ways to display a `UISlider`, a control to select a single value from a continuous range of values. Customize the appearance of a slider by assigning stretchable images for left-side tracking, right-side tracking, and the thumb. In this example, the track image is stretchable and is one pixel wide. Make the track images wider to provide rounded corners, but be sure to set these images' `capInsets` property to allow for the corners. The `configureCustomSlider` function sets up a custom slider: ``` swift -func configureCustomSlider() { +@available(iOS 15.0, *) +func configureCustomSlider(slider: UISlider) { + /** To keep the look the same betwen iOS and macOS: + For setMinimumTrackImage, setMaximumTrackImage, setThumbImage to work in Mac Catalyst, use UIBehavioralStyle as ".pad", + Available in macOS 12 or later (Mac Catalyst 15.0 or later). + Use this for controls that need to look the same between iOS and macOS. + */ + if traitCollection.userInterfaceIdiom == .mac { + slider.preferredBehavioralStyle = .pad + } + let leftTrackImage = UIImage(named: "slider_blue_track") - customSlider.setMinimumTrackImage(leftTrackImage, for: .normal) + slider.setMinimumTrackImage(leftTrackImage, for: .normal) let rightTrackImage = UIImage(named: "slider_green_track") - customSlider.setMaximumTrackImage(rightTrackImage, for: .normal) + slider.setMaximumTrackImage(rightTrackImage, for: .normal) // Set the sliding thumb image (normal and highlighted). - let thumbImageConfig = UIImage.SymbolConfiguration(scale: .large) + // + // For fun, choose a different image symbol configuraton for the thumb's image between macOS and iOS. + var thumbImageConfig: UIImage.SymbolConfiguration + if slider.traitCollection.userInterfaceIdiom == .mac { + thumbImageConfig = UIImage.SymbolConfiguration(scale: .large) + } else { + thumbImageConfig = UIImage.SymbolConfiguration(pointSize: 30, weight: .heavy, scale: .large) + } let thumbImage = UIImage(systemName: "circle.fill", withConfiguration: thumbImageConfig) - customSlider.setThumbImage(thumbImage, for: .normal) + slider.setThumbImage(thumbImage, for: .normal) + let thumbImageHighlighted = UIImage(systemName: "circle", withConfiguration: thumbImageConfig) - customSlider.setThumbImage(thumbImageHighlighted, for: .highlighted) + slider.setThumbImage(thumbImageHighlighted, for: .highlighted) - customSlider.minimumValue = 0 - customSlider.maximumValue = 100 - customSlider.isContinuous = false - customSlider.value = 84 + // Set the rest of the slider's attributes. + slider.minimumValue = 0 + slider.maximumValue = 100 + slider.isContinuous = false + slider.value = 84 - customSlider.addTarget(self, action: #selector(SliderViewController.sliderValueDidChange(_:)), for: .valueChanged) + slider.addTarget(self, action: #selector(SliderViewController.sliderValueDidChange(_:)), for: .valueChanged) } ``` @@ -193,7 +207,7 @@ override func viewDidLoad() { ## Add a Page Control Interface -Use a `UIPageControl` to structure an app's user interface. A page control is a specialized control that displays a horizontal series of dots, each of which corresponds to a page in the app’s document or other data-model entity. Customize a page control by setting its tint color for all the page-indicator dots, and for the current page-indicator dot. +Use a `UIPageControl` to structure an app's user interface. A *page control* is a specialized control that displays a horizontal series of dots, each of which corresponds to a page in the app’s document or other data-model entity. Customize a page control by setting its tint color for all the page-indicator dots, and for the current page-indicator dot. The `configurePageControl` function sets up a customized page control: @@ -210,23 +224,23 @@ func configurePageControl() { } ``` -## Add Menus to Your Controls +## Add Menus to Controls Attach menus to controls like `UIButton` and `UIBarButtonItem`. Create menus with the [`UIAction`](https://developer.apple.com/documentation/uikit/uiaction) class, and attach a menu to each control by setting the [`UIMenu`](https://developer.apple.com/documentation/uikit/uimenu) property. Attach a menu to a `UIButton` as shown here: ``` swift -func configureMenuButton() { - let buttonTitle = NSLocalizedString("Button", comment: "") - menuButton.setTitle(buttonTitle, for: .normal) +button.menu = UIMenu(children: [ + UIAction(title: String(format: NSLocalizedString("ItemTitle", comment: ""), "1"), + identifier: UIAction.Identifier(ButtonMenuActionIdentifiers.item1.rawValue), + handler: menuHandler), + UIAction(title: String(format: NSLocalizedString("ItemTitle", comment: ""), "2"), + identifier: UIAction.Identifier(ButtonMenuActionIdentifiers.item2.rawValue), + handler: menuHandler) +]) - let items = (1...5).map { - UIAction(title: String(format: NSLocalizedString("ItemTitle", comment: ""), $0.description), handler: menuHandler) - } - menuButton.menu = UIMenu(title: NSLocalizedString("ChooseItemTitle", comment: ""), children: items) - menuButton.showsMenuAsPrimaryAction = true -} +button.showsMenuAsPrimaryAction = true ``` Create a `UIBarButtonItem` with a menu attached as shown here: @@ -241,14 +255,36 @@ var customTitleBarButtonItem: UIBarButtonItem { } ``` +## Add Accessibility Support to Your Views + +VoiceOver and other system accessibility technologies use the information provided by views and controls to help all users navigate content. UIKit views include default accessibility support. Improve user experience by providing custom accessibility information. + +In this UIKitCatalog sample, several view controllers configure the `accessibilityType` and `accessibilityLabel` properties of their associated view. Picker view columns don't have labels, so the picker view asks its delegate for the corresponding accessibility information: + +``` swift +func pickerView(_ pickerView: UIPickerView, accessibilityLabelForComponent component: Int) -> String? { + + switch ColorComponent(rawValue: component)! { + case .red: + return NSLocalizedString("Red color component value", comment: "") + + case .green: + return NSLocalizedString("Green color component value", comment: "") + + case .blue: + return NSLocalizedString("Blue color component value", comment: "") + } +} +``` + ## Support Mac Catalyst -This sample app is built with Mac Catalyst, which means the sample runs on both iPad and Mac. This is achieved by selecting the Mac checkbox in Project Settings. For more about how Mac Catalyst works see [Mac Catalyst](https://developer.apple.com/mac-catalyst/). +This sample app is built with Mac Catalyst, which means the sample runs on both iOS and Mac. This is achieved by selecting the Mac checkbox in Project Settings. For more about how Mac Catalyst works see [Mac Catalyst](https://developer.apple.com/mac-catalyst/). When built for Mac Catalyst, this sample achieves: -* Interface Optimization for Mac — With Optimize Interface For the Mac project setting turned on, the app has full control of every pixel on the screen, and the app can adopt more controls specific to Mac. Building the sample for Mac Catalyst makes the app take advantage of the system features in macOS. The option Show Designed for iPad Run Destination allows this sample, as an iPad app, to run "as-is" on Apple silicon Macs. This requires macOS 11 and a Mac with Apple silicon. +* Interface Optimization for Mac. With Optimize Interface For the Mac project setting turned on, the app has full control of every pixel on the screen, and the app can adopt more controls specific to Mac. Building the sample for Mac Catalyst makes the app take advantage of the system features in macOS. The option Show Designed for iPad Run Destination allows this sample, as an iPad app, to run as-is on Apple silicon Macs. This requires macOS 11 and a Mac with Apple silicon. -* Navigation and title bar hiding — The sample app hides these to make the app appear more like a Mac app. It also changes other behaviors by using traitCollection's `userInterfaceIdiom` or the compilation conditional block that uses the `targetEnvironment():` platform condition. +* Navigation and title bar hiding. The sample app hides these to make the app appear more like a Mac app. It also changes other behaviors by using traitCollection's `userInterfaceIdiom`. -* Translucent background — By setting the split view controller's `primaryBackgroundStyle` to `.sidebar`, the primary view controller or side bar shows a blurred desktop behind its view. Setting this property has no effect when running in iOS. +* Translucent background. By setting the split view controller's `primaryBackgroundStyle` to `.sidebar`, the primary view controller or side bar shows a blurred desktop behind its view. Setting this property has no effect when running on iOS. diff --git a/UIKitCatalog.xcodeproj/project.pbxproj b/UIKitCatalog.xcodeproj/project.pbxproj index 7d1d2fe..ca52ec3 100644 --- a/UIKitCatalog.xcodeproj/project.pbxproj +++ b/UIKitCatalog.xcodeproj/project.pbxproj @@ -29,7 +29,10 @@ 2200544A18BC54F5002A6E8B /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2200543B18BC54F5002A6E8B /* WebViewController.swift */; }; 228DBA0818BC53F1002BA12A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 228DBA0718BC53F1002BA12A /* Assets.xcassets */; }; 3E5C084E1974991E00969DD7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3E5C08501974991E00969DD7 /* Main.storyboard */; }; + 530A4AB626AF352B00C0C649 /* BaseTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 530A4AB526AF352B00C0C649 /* BaseTableViewController.swift */; }; 5312D0F222848B0200048DE2 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 5312D0F022848B0200048DE2 /* Credits.rtf */; }; + 531AD242265E8B1200113EC6 /* VisualEffectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AD241265E8B1100113EC6 /* VisualEffectViewController.swift */; }; + 531AD245265E8B8700113EC6 /* VisualEffectViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 531AD243265E8B8700113EC6 /* VisualEffectViewController.storyboard */; }; 5340A1B62496CF64004F3666 /* DefaultToolbarViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5340A1B02496CF64004F3666 /* DefaultToolbarViewController.storyboard */; }; 5340A1B72496CF64004F3666 /* CustomToolbarViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5340A1B22496CF64004F3666 /* CustomToolbarViewController.storyboard */; }; 5340A1B82496CF64004F3666 /* TintedToolbarViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5340A1B42496CF64004F3666 /* TintedToolbarViewController.storyboard */; }; @@ -41,12 +44,15 @@ 5364C0992496C2B3009A9A52 /* DefaultPageControlViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5364C0952496C2B3009A9A52 /* DefaultPageControlViewController.storyboard */; }; 5364C09A2496C2B3009A9A52 /* CustomPageControlViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5364C0972496C2B3009A9A52 /* CustomPageControlViewController.storyboard */; }; 53654E232298881200B999C7 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53654E222298881200B999C7 /* WebKit.framework */; }; + 5387556B2660933A0041A1B4 /* PointerInteractionButtonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5387556A2660933A0041A1B4 /* PointerInteractionButtonViewController.swift */; }; + 53875571266095990041A1B4 /* PointerInteractionButtonViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5387556F266095990041A1B4 /* PointerInteractionButtonViewController.storyboard */; }; 538B36F41F2A8E06002AE100 /* DatePickerController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 538B36F21F2A8D97002AE100 /* DatePickerController.storyboard */; }; 538B36F71F2A8E8A002AE100 /* TextViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 538B36F51F2A8E66002AE100 /* TextViewController.storyboard */; }; 539029FF1F2A53AD009775E3 /* AlertControllerViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 539029FD1F2A53AD009775E3 /* AlertControllerViewController.storyboard */; }; 539C6BAE1F27F4980006C5A9 /* ActivityIndicatorViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 539C6BA81F27F4980006C5A9 /* ActivityIndicatorViewController.storyboard */; }; 539C6BAF1F27F4980006C5A9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 539C6BAA1F27F4980006C5A9 /* LaunchScreen.storyboard */; }; 539C6BB01F27F4980006C5A9 /* WebViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 539C6BAC1F27F4980006C5A9 /* WebViewController.storyboard */; }; + 53A2264B26C1AE7000C0EF3F /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 53A2264926C1A70800C0EF3F /* Localizable.stringsdict */; }; 53A266B52491ED77008EADBB /* ImagePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A266AE2491ED77008EADBB /* ImagePickerViewController.swift */; }; 53A266B62491ED77008EADBB /* CustomPageControlViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A266AF2491ED77008EADBB /* CustomPageControlViewController.swift */; }; 53A266B82491ED77008EADBB /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A266B12491ED77008EADBB /* ColorPickerViewController.swift */; }; @@ -57,6 +63,9 @@ 53B791DA1F85505400AB2FA6 /* content.html in Resources */ = {isa = PBXBuildFile; fileRef = 53B791D41F854B4700AB2FA6 /* content.html */; }; 53B791DB1F85505700AB2FA6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 53B791D61F854B4700AB2FA6 /* InfoPlist.strings */; }; 53B791DC1F85505A00AB2FA6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 53B791D81F854B4800AB2FA6 /* Localizable.strings */; }; + 53CB2C59265FFE9900155325 /* ButtonViewController+Configs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CB2C58265FFE9900155325 /* ButtonViewController+Configs.swift */; }; + 53CB2C60266003A800155325 /* MenuButtonViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53CB2C5E266003A800155325 /* MenuButtonViewController.storyboard */; }; + 53CB2C62266003B200155325 /* MenuButtonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53CB2C61266003B200155325 /* MenuButtonViewController.swift */; }; 53CE5AD81F2A89E500D8A656 /* ButtonViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53CE5AD61F2A89E500D8A656 /* ButtonViewController.storyboard */; }; 53CE5ADE1F2A8A3D00D8A656 /* ImageViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53CE5ADC1F2A8A3D00D8A656 /* ImageViewController.storyboard */; }; 53CE5AE61F2A8AEF00D8A656 /* PickerViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53CE5AE41F2A8AEF00D8A656 /* PickerViewController.storyboard */; }; @@ -67,6 +76,9 @@ 53CE5AF51F2A8BB000D8A656 /* StepperViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53CE5AF31F2A8BB000D8A656 /* StepperViewController.storyboard */; }; 53CE5AF81F2A8BD000D8A656 /* SwitchViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53CE5AF61F2A8BD000D8A656 /* SwitchViewController.storyboard */; }; 53CE5AFB1F2A8BEB00D8A656 /* TextFieldViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53CE5AF91F2A8BEB00D8A656 /* TextFieldViewController.storyboard */; }; + 53D054A526790B6E00CD8B1A /* SymbolViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53D054A326790B6E00CD8B1A /* SymbolViewController.storyboard */; }; + 53D054A726790B7D00CD8B1A /* SymbolViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D054A626790B7D00CD8B1A /* SymbolViewController.swift */; }; + 53F57011265761D500458712 /* CaseElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F57010265761D500458712 /* CaseElement.swift */; }; B50F41081B1D284700E5147D /* StackViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50F41071B1D284700E5147D /* StackViewController.swift */; }; /* End PBXBuildFile section */ @@ -96,7 +108,10 @@ 228DB9F718BC53F1002BA12A /* UIKitCatalog-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "UIKitCatalog-Info.plist"; sourceTree = ""; }; 228DBA0718BC53F1002BA12A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 3E5C084F1974991E00969DD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 530A4AB526AF352B00C0C649 /* BaseTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTableViewController.swift; sourceTree = ""; }; 5312D0F122848B0200048DE2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = Base; path = Base.lproj/Credits.rtf; sourceTree = ""; }; + 531AD241265E8B1100113EC6 /* VisualEffectViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VisualEffectViewController.swift; sourceTree = ""; }; + 531AD244265E8B8700113EC6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/VisualEffectViewController.storyboard; sourceTree = ""; }; 5340A1B12496CF64004F3666 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/DefaultToolbarViewController.storyboard; sourceTree = ""; }; 5340A1B32496CF64004F3666 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/CustomToolbarViewController.storyboard; sourceTree = ""; }; 5340A1B52496CF64004F3666 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/TintedToolbarViewController.storyboard; sourceTree = ""; }; @@ -108,12 +123,15 @@ 5364C0962496C2B3009A9A52 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/DefaultPageControlViewController.storyboard; sourceTree = ""; }; 5364C0982496C2B3009A9A52 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/CustomPageControlViewController.storyboard; sourceTree = ""; }; 53654E222298881200B999C7 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + 5387556A2660933A0041A1B4 /* PointerInteractionButtonViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PointerInteractionButtonViewController.swift; sourceTree = ""; }; + 53875570266095990041A1B4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PointerInteractionButtonViewController.storyboard; sourceTree = ""; }; 538B36F31F2A8D97002AE100 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/DatePickerController.storyboard; sourceTree = ""; }; 538B36F61F2A8E66002AE100 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/TextViewController.storyboard; sourceTree = ""; }; 539029FE1F2A53AD009775E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/AlertControllerViewController.storyboard; sourceTree = ""; }; 539C6BA91F27F4980006C5A9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/ActivityIndicatorViewController.storyboard; sourceTree = ""; }; 539C6BAB1F27F4980006C5A9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 539C6BAD1F27F4980006C5A9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/WebViewController.storyboard; sourceTree = ""; }; + 53A2264A26C1A70800C0EF3F /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = Base; path = UIKitCatalog/Base.lproj/Localizable.stringsdict; sourceTree = ""; }; 53A266AE2491ED77008EADBB /* ImagePickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerViewController.swift; sourceTree = ""; }; 53A266AF2491ED77008EADBB /* CustomPageControlViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomPageControlViewController.swift; sourceTree = ""; }; 53A266B12491ED77008EADBB /* ColorPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = ""; }; @@ -124,6 +142,9 @@ 53B791D51F854B4700AB2FA6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.html; name = Base; path = Base.lproj/content.html; sourceTree = ""; }; 53B791D71F854B4700AB2FA6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; 53B791D91F854B4800AB2FA6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; + 53CB2C58265FFE9900155325 /* ButtonViewController+Configs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ButtonViewController+Configs.swift"; sourceTree = ""; }; + 53CB2C5F266003A800155325 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MenuButtonViewController.storyboard; sourceTree = ""; }; + 53CB2C61266003B200155325 /* MenuButtonViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuButtonViewController.swift; sourceTree = ""; }; 53CE5AD71F2A89E500D8A656 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/ButtonViewController.storyboard; sourceTree = ""; }; 53CE5ADD1F2A8A3D00D8A656 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/ImageViewController.storyboard; sourceTree = ""; }; 53CE5AE51F2A8AEF00D8A656 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PickerViewController.storyboard; sourceTree = ""; }; @@ -134,7 +155,10 @@ 53CE5AF41F2A8BB000D8A656 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/StepperViewController.storyboard; sourceTree = ""; }; 53CE5AF71F2A8BD000D8A656 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/SwitchViewController.storyboard; sourceTree = ""; }; 53CE5AFA1F2A8BEB00D8A656 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/TextFieldViewController.storyboard; sourceTree = ""; }; + 53D054A426790B6E00CD8B1A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/SymbolViewController.storyboard; sourceTree = ""; }; + 53D054A626790B7D00CD8B1A /* SymbolViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SymbolViewController.swift; sourceTree = ""; }; 53DDE73C22776382000006CF /* UIKitCatalog.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = UIKitCatalog.entitlements; sourceTree = ""; }; + 53F57010265761D500458712 /* CaseElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaseElement.swift; sourceTree = ""; }; 64D8CD77C30F3CED2D55B8F2 /* LICENSE.txt */ = {isa = PBXFileReference; includeInIndex = 1; path = LICENSE.txt; sourceTree = ""; }; 8A04EFECD9880EE89ADA2FE8 /* SampleCode.xcconfig */ = {isa = PBXFileReference; name = SampleCode.xcconfig; path = Configuration/SampleCode.xcconfig; sourceTree = ""; }; B50F41071B1D284700E5147D /* StackViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackViewController.swift; sourceTree = ""; }; @@ -203,6 +227,7 @@ 53B791D41F854B4700AB2FA6 /* content.html */, 53B791D61F854B4700AB2FA6 /* InfoPlist.strings */, 53B791D81F854B4800AB2FA6 /* Localizable.strings */, + 53A2264926C1A70800C0EF3F /* Localizable.stringsdict */, 5312D0F022848B0200048DE2 /* Credits.rtf */, ); name = "Supporting Files"; @@ -235,20 +260,16 @@ 3E1DA7601931CC99000114A9 /* Controls */ = { isa = PBXGroup; children = ( - 2200542118BC54EC002A6E8B /* ButtonViewController.swift */, - 53CE5AD61F2A89E500D8A656 /* ButtonViewController.storyboard */, + 53D34692265D86FF00D0B553 /* Button */, + 53CB2C5A2660038E00155325 /* Menu Button */, + 53875569266093290041A1B4 /* Pointer Interaction Button */, 5364C0942496C22D009A9A52 /* Page Control */, 22B7BB9718BC601D006C4AD5 /* Search */, - 2200543318BC54F5002A6E8B /* SegmentedControlViewController.swift */, - 53CE5AEA1F2A8B2F00D8A656 /* SegmentedControlViewController.storyboard */, - 2200543418BC54F5002A6E8B /* SliderViewController.swift */, - 53CE5AED1F2A8B4F00D8A656 /* SliderViewController.storyboard */, - 2200543618BC54F5002A6E8B /* StepperViewController.swift */, - 53CE5AF31F2A8BB000D8A656 /* StepperViewController.storyboard */, - 2200543718BC54F5002A6E8B /* SwitchViewController.swift */, - 53CE5AF61F2A8BD000D8A656 /* SwitchViewController.storyboard */, - 2200543818BC54F5002A6E8B /* TextFieldViewController.swift */, - 53CE5AF91F2A8BEB00D8A656 /* TextFieldViewController.storyboard */, + 53D34693265D871500D0B553 /* Segmented */, + 53D34694265D872A00D0B553 /* Slider */, + 53D428AA26826A8D001A0414 /* Stepper */, + 53D428AC26826AB1001A0414 /* Switch */, + 53D428AB26826A9C001A0414 /* TextField */, ); name = Controls; sourceTree = ""; @@ -259,6 +280,8 @@ 2200541C18BC54E8002A6E8B /* AppDelegate.swift */, 535D32B124970EF10011E153 /* SceneDelegate.swift */, 5364C08A249696D7009A9A52 /* OutlineViewController.swift */, + 53F57010265761D500458712 /* CaseElement.swift */, + 530A4AB526AF352B00C0C649 /* BaseTableViewController.swift */, ); name = Application; sourceTree = ""; @@ -282,22 +305,39 @@ name = Frameworks; sourceTree = ""; }; + 53875569266093290041A1B4 /* Pointer Interaction Button */ = { + isa = PBXGroup; + children = ( + 5387556A2660933A0041A1B4 /* PointerInteractionButtonViewController.swift */, + 5387556F266095990041A1B4 /* PointerInteractionButtonViewController.storyboard */, + ); + name = "Pointer Interaction Button"; + sourceTree = ""; + }; + 53927674268684F900CB7664 /* ProgressView */ = { + isa = PBXGroup; + children = ( + 2200543218BC54F5002A6E8B /* ProgressViewController.swift */, + 53CE5AE71F2A8B1000D8A656 /* ProgressViewController.storyboard */, + ); + name = ProgressView; + sourceTree = ""; + }; 539383DE2492800100A489A9 /* Views */ = { isa = PBXGroup; children = ( - 2200541A18BC54E8002A6E8B /* ActivityIndicatorViewController.swift */, - 539C6BA81F27F4980006C5A9 /* ActivityIndicatorViewController.storyboard */, + 53D428AD2682C58C001A0414 /* ActivityIndicator */, 2200541B18BC54E8002A6E8B /* AlertControllerViewController.swift */, 539029FD1F2A53AD009775E3 /* AlertControllerViewController.storyboard */, - 2200542D18BC54F5002A6E8B /* ImageViewController.swift */, - 53CE5ADC1F2A8A3D00D8A656 /* ImageViewController.storyboard */, - 2200543218BC54F5002A6E8B /* ProgressViewController.swift */, - 53CE5AE71F2A8B1000D8A656 /* ProgressViewController.storyboard */, - B50F41071B1D284700E5147D /* StackViewController.swift */, - 53CE5AF01F2A8B8300D8A656 /* StackViewController.storyboard */, 2200543918BC54F5002A6E8B /* TextViewController.swift */, 538B36F51F2A8E66002AE100 /* TextViewController.storyboard */, + 53D0549F26790A3200CD8B1A /* Image View */, + 53927674268684F900CB7664 /* ProgressView */, + B50F41071B1D284700E5147D /* StackViewController.swift */, + 53CE5AF01F2A8B8300D8A656 /* StackViewController.storyboard */, 22B7BB9818BC6024006C4AD5 /* Toolbar */, + 531AD241265E8B1100113EC6 /* VisualEffectViewController.swift */, + 531AD243265E8B8700113EC6 /* VisualEffectViewController.storyboard */, 2200543B18BC54F5002A6E8B /* WebViewController.swift */, 539C6BAC1F27F4980006C5A9 /* WebViewController.storyboard */, ); @@ -321,6 +361,90 @@ name = Pickers; sourceTree = ""; }; + 53CB2C5A2660038E00155325 /* Menu Button */ = { + isa = PBXGroup; + children = ( + 53CB2C61266003B200155325 /* MenuButtonViewController.swift */, + 53CB2C5E266003A800155325 /* MenuButtonViewController.storyboard */, + ); + name = "Menu Button"; + sourceTree = ""; + }; + 53D0549F26790A3200CD8B1A /* Image View */ = { + isa = PBXGroup; + children = ( + 2200542D18BC54F5002A6E8B /* ImageViewController.swift */, + 53CE5ADC1F2A8A3D00D8A656 /* ImageViewController.storyboard */, + 53D054A626790B7D00CD8B1A /* SymbolViewController.swift */, + 53D054A326790B6E00CD8B1A /* SymbolViewController.storyboard */, + ); + name = "Image View"; + sourceTree = ""; + }; + 53D34692265D86FF00D0B553 /* Button */ = { + isa = PBXGroup; + children = ( + 2200542118BC54EC002A6E8B /* ButtonViewController.swift */, + 53CB2C58265FFE9900155325 /* ButtonViewController+Configs.swift */, + 53CE5AD61F2A89E500D8A656 /* ButtonViewController.storyboard */, + ); + name = Button; + sourceTree = ""; + }; + 53D34693265D871500D0B553 /* Segmented */ = { + isa = PBXGroup; + children = ( + 2200543318BC54F5002A6E8B /* SegmentedControlViewController.swift */, + 53CE5AEA1F2A8B2F00D8A656 /* SegmentedControlViewController.storyboard */, + ); + name = Segmented; + sourceTree = ""; + }; + 53D34694265D872A00D0B553 /* Slider */ = { + isa = PBXGroup; + children = ( + 2200543418BC54F5002A6E8B /* SliderViewController.swift */, + 53CE5AED1F2A8B4F00D8A656 /* SliderViewController.storyboard */, + ); + name = Slider; + sourceTree = ""; + }; + 53D428AA26826A8D001A0414 /* Stepper */ = { + isa = PBXGroup; + children = ( + 2200543618BC54F5002A6E8B /* StepperViewController.swift */, + 53CE5AF31F2A8BB000D8A656 /* StepperViewController.storyboard */, + ); + name = Stepper; + sourceTree = ""; + }; + 53D428AB26826A9C001A0414 /* TextField */ = { + isa = PBXGroup; + children = ( + 2200543818BC54F5002A6E8B /* TextFieldViewController.swift */, + 53CE5AF91F2A8BEB00D8A656 /* TextFieldViewController.storyboard */, + ); + name = TextField; + sourceTree = ""; + }; + 53D428AC26826AB1001A0414 /* Switch */ = { + isa = PBXGroup; + children = ( + 2200543718BC54F5002A6E8B /* SwitchViewController.swift */, + 53CE5AF61F2A8BD000D8A656 /* SwitchViewController.storyboard */, + ); + name = Switch; + sourceTree = ""; + }; + 53D428AD2682C58C001A0414 /* ActivityIndicator */ = { + isa = PBXGroup; + children = ( + 2200541A18BC54E8002A6E8B /* ActivityIndicatorViewController.swift */, + 539C6BA81F27F4980006C5A9 /* ActivityIndicatorViewController.storyboard */, + ); + name = ActivityIndicator; + sourceTree = ""; + }; D9361AEE59ED9397893793F6 /* LICENSE */ = { isa = PBXGroup; children = ( @@ -399,12 +523,14 @@ 53A266C22491ED9E008EADBB /* ImagePickerViewController.storyboard in Resources */, 5364C0992496C2B3009A9A52 /* DefaultPageControlViewController.storyboard in Resources */, 5340A1B72496CF64004F3666 /* CustomToolbarViewController.storyboard in Resources */, + 53875571266095990041A1B4 /* PointerInteractionButtonViewController.storyboard in Resources */, 53CE5AE61F2A8AEF00D8A656 /* PickerViewController.storyboard in Resources */, 53B791DA1F85505400AB2FA6 /* content.html in Resources */, 3E5C084E1974991E00969DD7 /* Main.storyboard in Resources */, 53CE5AEC1F2A8B2F00D8A656 /* SegmentedControlViewController.storyboard in Resources */, 5364C0932496BEFD009A9A52 /* CustomSearchBarViewController.storyboard in Resources */, 53B791DB1F85505700AB2FA6 /* InfoPlist.strings in Resources */, + 53CB2C60266003A800155325 /* MenuButtonViewController.storyboard in Resources */, 539C6BAE1F27F4980006C5A9 /* ActivityIndicatorViewController.storyboard in Resources */, 53CE5AFB1F2A8BEB00D8A656 /* TextFieldViewController.storyboard in Resources */, 53B791DC1F85505A00AB2FA6 /* Localizable.strings in Resources */, @@ -415,9 +541,12 @@ 538B36F71F2A8E8A002AE100 /* TextViewController.storyboard in Resources */, 539C6BAF1F27F4980006C5A9 /* LaunchScreen.storyboard in Resources */, 5312D0F222848B0200048DE2 /* Credits.rtf in Resources */, + 53D054A526790B6E00CD8B1A /* SymbolViewController.storyboard in Resources */, 539029FF1F2A53AD009775E3 /* AlertControllerViewController.storyboard in Resources */, 53CE5AF51F2A8BB000D8A656 /* StepperViewController.storyboard in Resources */, + 531AD245265E8B8700113EC6 /* VisualEffectViewController.storyboard in Resources */, 53CE5AF81F2A8BD000D8A656 /* SwitchViewController.storyboard in Resources */, + 53A2264B26C1AE7000C0EF3F /* Localizable.stringsdict in Resources */, 5340A1B62496CF64004F3666 /* DefaultToolbarViewController.storyboard in Resources */, 53CE5ADE1F2A8A3D00D8A656 /* ImageViewController.storyboard in Resources */, ); @@ -437,6 +566,8 @@ 2200544218BC54F5002A6E8B /* SegmentedControlViewController.swift in Sources */, 2200544A18BC54F5002A6E8B /* WebViewController.swift in Sources */, 2200542018BC54E8002A6E8B /* AppDelegate.swift in Sources */, + 531AD242265E8B1200113EC6 /* VisualEffectViewController.swift in Sources */, + 53CB2C62266003B200155325 /* MenuButtonViewController.swift in Sources */, 5340A1B92496D670004F3666 /* PickerViewController.swift in Sources */, 53A266B92491ED77008EADBB /* FontPickerViewController.swift in Sources */, 2200541F18BC54E8002A6E8B /* AlertControllerViewController.swift in Sources */, @@ -445,16 +576,21 @@ 2200541E18BC54E8002A6E8B /* ActivityIndicatorViewController.swift in Sources */, 2200544618BC54F5002A6E8B /* SwitchViewController.swift in Sources */, B50F41081B1D284700E5147D /* StackViewController.swift in Sources */, + 530A4AB626AF352B00C0C649 /* BaseTableViewController.swift in Sources */, 535D32B224970EF10011E153 /* SceneDelegate.swift in Sources */, + 53F57011265761D500458712 /* CaseElement.swift in Sources */, 2200544118BC54F5002A6E8B /* ProgressViewController.swift in Sources */, 2200544718BC54F5002A6E8B /* TextFieldViewController.swift in Sources */, 2200544818BC54F5002A6E8B /* TextViewController.swift in Sources */, + 5387556B2660933A0041A1B4 /* PointerInteractionButtonViewController.swift in Sources */, 2200542B18BC54EC002A6E8B /* DefaultSearchBarViewController.swift in Sources */, 2200544518BC54F5002A6E8B /* StepperViewController.swift in Sources */, + 53D054A726790B7D00CD8B1A /* SymbolViewController.swift in Sources */, 53A266B62491ED77008EADBB /* CustomPageControlViewController.swift in Sources */, 53A266B82491ED77008EADBB /* ColorPickerViewController.swift in Sources */, 2200542818BC54EC002A6E8B /* CustomSearchBarViewController.swift in Sources */, 53A266B52491ED77008EADBB /* ImagePickerViewController.swift in Sources */, + 53CB2C59265FFE9900155325 /* ButtonViewController+Configs.swift in Sources */, 2200542918BC54EC002A6E8B /* CustomToolbarViewController.swift in Sources */, 2200542A18BC54EC002A6E8B /* DatePickerController.swift in Sources */, 2200542718BC54EC002A6E8B /* ButtonViewController.swift in Sources */, @@ -480,6 +616,14 @@ name = Credits.rtf; sourceTree = ""; }; + 531AD243265E8B8700113EC6 /* VisualEffectViewController.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 531AD244265E8B8700113EC6 /* Base */, + ); + name = VisualEffectViewController.storyboard; + sourceTree = ""; + }; 5340A1B02496CF64004F3666 /* DefaultToolbarViewController.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -536,6 +680,14 @@ name = CustomPageControlViewController.storyboard; sourceTree = ""; }; + 5387556F266095990041A1B4 /* PointerInteractionButtonViewController.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 53875570266095990041A1B4 /* Base */, + ); + name = PointerInteractionButtonViewController.storyboard; + sourceTree = ""; + }; 538B36F21F2A8D97002AE100 /* DatePickerController.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -584,6 +736,14 @@ name = WebViewController.storyboard; sourceTree = ""; }; + 53A2264926C1A70800C0EF3F /* Localizable.stringsdict */ = { + isa = PBXVariantGroup; + children = ( + 53A2264A26C1A70800C0EF3F /* Base */, + ); + name = Localizable.stringsdict; + sourceTree = ""; + }; 53A266BA2491ED9E008EADBB /* ImagePickerViewController.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -632,6 +792,14 @@ name = Localizable.strings; sourceTree = ""; }; + 53CB2C5E266003A800155325 /* MenuButtonViewController.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 53CB2C5F266003A800155325 /* Base */, + ); + name = MenuButtonViewController.storyboard; + sourceTree = ""; + }; 53CE5AD61F2A89E500D8A656 /* ButtonViewController.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -712,6 +880,14 @@ name = TextFieldViewController.storyboard; sourceTree = ""; }; + 53D054A326790B6E00CD8B1A /* SymbolViewController.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 53D054A426790B6E00CD8B1A /* Base */, + ); + name = SymbolViewController.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -831,9 +1007,9 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/UIKitCatalog/UIKitCatalog-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; - MARKETING_VERSION = 16; + MARKETING_VERSION = 17; PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.${PRODUCT_NAME:rfc1034identifier}${SAMPLE_CODE_DISAMBIGUATOR}"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -854,9 +1030,9 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/UIKitCatalog/UIKitCatalog-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.1; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; - MARKETING_VERSION = 16; + MARKETING_VERSION = 17; PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.${PRODUCT_NAME:rfc1034identifier}${SAMPLE_CODE_DISAMBIGUATOR}"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/UIKitCatalog/ActivityIndicatorViewController.swift b/UIKitCatalog/ActivityIndicatorViewController.swift index 4c30f20..b97a126 100755 --- a/UIKitCatalog/ActivityIndicatorViewController.swift +++ b/UIKitCatalog/ActivityIndicatorViewController.swift @@ -7,78 +7,75 @@ A view controller that demonstrates how to use `UIActivityIndicatorView`. import UIKit -class ActivityIndicatorViewController: UITableViewController { - // MARK: - Properties - - @IBOutlet weak var defaultSmallActivityIndicatorView: UIActivityIndicatorView! - @IBOutlet weak var defaultLargeActivityIndicatorView: UIActivityIndicatorView! +class ActivityIndicatorViewController: BaseTableViewController { - @IBOutlet weak var tintedSmallActivityIndicatorView: UIActivityIndicatorView! - @IBOutlet weak var tintedLargeActivityIndicatorView: UIActivityIndicatorView! + // Cell identifier for each activity indicator table view cell. + enum ActivityIndicatorKind: String, CaseIterable { + case mediumIndicator + case largeIndicator + case mediumTintedIndicator + case largeTintedIndicator + } - // MARK: - View Life Cycle - override func viewDidLoad() { super.viewDidLoad() + + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("MediumIndicatorTitle", comment: ""), + cellID: ActivityIndicatorKind.mediumIndicator.rawValue, + configHandler: configureMediumActivityIndicatorView), + CaseElement(title: NSLocalizedString("LargeIndicatorTitle", comment: ""), + cellID: ActivityIndicatorKind.largeIndicator.rawValue, + configHandler: configureLargeActivityIndicatorView) + ]) - configureDefaultActivityIndicatorView() - configureTintedActivityIndicatorView() - - // When the activity is done, be sure to use UIActivityIndicatorView.stopAnimating(). + if traitCollection.userInterfaceIdiom != .mac { + // Tinted activity indicators available only on iOS. + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("MediumTintedIndicatorTitle", comment: ""), + cellID: ActivityIndicatorKind.mediumTintedIndicator.rawValue, + configHandler: configureMediumTintedActivityIndicatorView), + CaseElement(title: NSLocalizedString("LargeTintedIndicatorTitle", comment: ""), + cellID: ActivityIndicatorKind.largeTintedIndicator.rawValue, + configHandler: configureLargeTintedActivityIndicatorView) + ]) + } } // MARK: - Configuration + + func configureMediumActivityIndicatorView(_ activityIndicator: UIActivityIndicatorView) { + activityIndicator.style = UIActivityIndicatorView.Style.medium + activityIndicator.hidesWhenStopped = true + + activityIndicator.startAnimating() + // When the activity is done, be sure to use UIActivityIndicatorView.stopAnimating(). + } + + func configureLargeActivityIndicatorView(_ activityIndicator: UIActivityIndicatorView) { + activityIndicator.style = UIActivityIndicatorView.Style.large + activityIndicator.hidesWhenStopped = true - func configureDefaultActivityIndicatorView() { - defaultSmallActivityIndicatorView.style = UIActivityIndicatorView.Style.medium - defaultLargeActivityIndicatorView.style = UIActivityIndicatorView.Style.large - - defaultSmallActivityIndicatorView.startAnimating() - defaultLargeActivityIndicatorView.startAnimating() - - defaultSmallActivityIndicatorView.hidesWhenStopped = true - defaultLargeActivityIndicatorView.hidesWhenStopped = true + activityIndicator.startAnimating() + // When the activity is done, be sure to use UIActivityIndicatorView.stopAnimating(). } - func configureTintedActivityIndicatorView() { - tintedSmallActivityIndicatorView.style = UIActivityIndicatorView.Style.medium - tintedLargeActivityIndicatorView.style = UIActivityIndicatorView.Style.large - - tintedSmallActivityIndicatorView.color = UIColor.systemPurple - tintedLargeActivityIndicatorView.color = UIColor.systemPurple - - tintedSmallActivityIndicatorView.startAnimating() - tintedLargeActivityIndicatorView.startAnimating() + func configureMediumTintedActivityIndicatorView(_ activityIndicator: UIActivityIndicatorView) { + activityIndicator.style = UIActivityIndicatorView.Style.medium + activityIndicator.hidesWhenStopped = true + activityIndicator.color = UIColor.systemPurple + + activityIndicator.startAnimating() + // When the activity is done, be sure to use UIActivityIndicatorView.stopAnimating(). } - // MARK: - UITableViewDataSource - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - #if targetEnvironment(macCatalyst) - // Don't show tinted activitiy indicator for macOS, it does not exist. - if section == 1 { - return "" - } else { - return super.tableView(tableView, titleForHeaderInSection: section) - } - #else - return super.tableView(tableView, titleForHeaderInSection: section) - #endif + func configureLargeTintedActivityIndicatorView(_ activityIndicator: UIActivityIndicatorView) { + activityIndicator.style = UIActivityIndicatorView.Style.large + activityIndicator.hidesWhenStopped = true + activityIndicator.color = UIColor.systemPurple + + activityIndicator.startAnimating() + // When the activity is done, be sure to use UIActivityIndicatorView.stopAnimating(). } - // MARK: - UITableViewDelegate - - override func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - #if targetEnvironment(macCatalyst) - // Don't show tinted activity indicator for macOS, it does not exist. - if indexPath.section == 1 { - return 0 - } else { - return super.tableView(tableView, heightForRowAt: indexPath) - } - #else - return super.tableView(tableView, heightForRowAt: indexPath) - #endif - } } diff --git a/UIKitCatalog/AlertControllerViewController.swift b/UIKitCatalog/AlertControllerViewController.swift index ee07eb8..93ed339 100755 --- a/UIKitCatalog/AlertControllerViewController.swift +++ b/UIKitCatalog/AlertControllerViewController.swift @@ -32,21 +32,21 @@ class AlertControllerViewController: UITableViewController { case howOtherActionSheet } - private var textDidChangeObserver: NSObjectProtocol! - + private var textDidChangeObserver: Any? = nil + // MARK: - UIAlertControllerStyleAlert Style Alerts /// Show an alert with an "OK" button. func showSimpleAlert() { let title = NSLocalizedString("A Short Title is Best", comment: "") - let message = NSLocalizedString("A message should be a short, complete sentence.", comment: "") + let message = NSLocalizedString("A message needs to be a short, complete sentence.", comment: "") let cancelButtonTitle = NSLocalizedString("OK", comment: "") let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) // Create the action. let cancelAction = UIAlertAction(title: cancelButtonTitle, style: .cancel) { _ in - print("The simple alert's cancel action occurred.") + Swift.debugPrint("The simple alert's cancel action occurred.") } // Add the action. @@ -58,7 +58,7 @@ class AlertControllerViewController: UITableViewController { /// Show an alert with an "OK" and "Cancel" button. func showOkayCancelAlert() { let title = NSLocalizedString("A Short Title is Best", comment: "") - let message = NSLocalizedString("A message should be a short, complete sentence.", comment: "") + let message = NSLocalizedString("A message needs to be a short, complete sentence.", comment: "") let cancelButtonTitle = NSLocalizedString("Cancel", comment: "") let otherButtonTitle = NSLocalizedString("OK", comment: "") @@ -66,11 +66,11 @@ class AlertControllerViewController: UITableViewController { // Create the actions. let cancelAction = UIAlertAction(title: cancelButtonTitle, style: .cancel) { _ in - print("The \"OK/Cancel\" alert's cancel action occurred.") + Swift.debugPrint("The \"OK/Cancel\" alert's cancel action occurred.") } let otherAction = UIAlertAction(title: otherButtonTitle, style: .default) { _ in - print("The \"OK/Cancel\" alert's other action occurred.") + Swift.debugPrint("The \"OK/Cancel\" alert's other action occurred.") } // Add the actions. @@ -83,7 +83,7 @@ class AlertControllerViewController: UITableViewController { /// Show an alert with two custom buttons. func showOtherAlert() { let title = NSLocalizedString("A Short Title is Best", comment: "") - let message = NSLocalizedString("A message should be a short, complete sentence.", comment: "") + let message = NSLocalizedString("A message needs to be a short, complete sentence.", comment: "") let cancelButtonTitle = NSLocalizedString("Cancel", comment: "") let otherButtonTitleOne = NSLocalizedString("Choice One", comment: "") let otherButtonTitleTwo = NSLocalizedString("Choice Two", comment: "") @@ -92,15 +92,15 @@ class AlertControllerViewController: UITableViewController { // Create the actions. let cancelAction = UIAlertAction(title: cancelButtonTitle, style: .cancel) { _ in - print("The \"Other\" alert's cancel action occurred.") + Swift.debugPrint("The \"Other\" alert's cancel action occurred.") } let otherButtonOneAction = UIAlertAction(title: otherButtonTitleOne, style: .default) { _ in - print("The \"Other\" alert's other button one action occurred.") + Swift.debugPrint("The \"Other\" alert's other button one action occurred.") } let otherButtonTwoAction = UIAlertAction(title: otherButtonTitleTwo, style: .default) { _ in - print("The \"Other\" alert's other button two action occurred.") + Swift.debugPrint("The \"Other\" alert's other button two action occurred.") } // Add the actions. @@ -114,7 +114,7 @@ class AlertControllerViewController: UITableViewController { /// Show a text entry alert with two custom buttons. func showTextEntryAlert() { let title = NSLocalizedString("A Short Title is Best", comment: "") - let message = NSLocalizedString("A message should be a short, complete sentence.", comment: "") + let message = NSLocalizedString("A message needs to be a short, complete sentence.", comment: "") let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) @@ -126,12 +126,12 @@ class AlertControllerViewController: UITableViewController { // Create the actions. let cancelButtonTitle = NSLocalizedString("Cancel", comment: "") let cancelAction = UIAlertAction(title: cancelButtonTitle, style: .cancel) { _ in - print("The \"Text Entry\" alert's cancel action occurred.") + Swift.debugPrint("The \"Text Entry\" alert's cancel action occurred.") } let otherButtonTitle = NSLocalizedString("OK", comment: "") let otherAction = UIAlertAction(title: otherButtonTitle, style: .default) { _ in - print("The \"Text Entry\" alert's other action occurred.") + Swift.debugPrint("The \"Text Entry\" alert's other action occurred.") } // Add the actions. @@ -144,7 +144,7 @@ class AlertControllerViewController: UITableViewController { /// Show a secure text entry alert with two custom buttons. func showSecureTextEntryAlert() { let title = NSLocalizedString("A Short Title is Best", comment: "") - let message = NSLocalizedString("A message should be a short, complete sentence.", comment: "") + let message = NSLocalizedString("A message needs to be a short, complete sentence.", comment: "") let cancelButtonTitle = NSLocalizedString("Cancel", comment: "") let otherButtonTitle = NSLocalizedString("OK", comment: "") @@ -152,34 +152,40 @@ class AlertControllerViewController: UITableViewController { // Add the text field for the secure text entry. alertController.addTextField { textField in - /** Listen for changes to the text field's text so that we can toggle the current + if let observer = self.textDidChangeObserver { + NotificationCenter.default.removeObserver(observer) + } + /** Listen for changes to the text field's text so that we can toggle the current action's enabled property based on whether the user has entered a sufficiently secure entry. */ - self.textDidChangeObserver = NotificationCenter.default.addObserver( - forName: UITextField.textDidChangeNotification, - object: textField, - queue: OperationQueue.main) { (notification) in - if let textField = notification.object as? UITextField { - // Enforce a minimum length of >= 5 characters for secure text alerts. - if let text = textField.text { - self.secureTextAlertAction!.isEnabled = text.count >= 5 - } else { - self.secureTextAlertAction!.isEnabled = false - } - } - } - + self.textDidChangeObserver = + NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, + object: textField, + queue: OperationQueue.main, + using: { (notification) in + if let textField = notification.object as? UITextField { + // Enforce a minimum length of >= 5 characters for secure text alerts. + if let alertAction = self.secureTextAlertAction { + if let text = textField.text { + alertAction.isEnabled = text.count >= 5 + } else { + alertAction.isEnabled = false + } + } + } + }) + textField.isSecureTextEntry = true } // Create the actions. let cancelAction = UIAlertAction(title: cancelButtonTitle, style: .cancel) { _ in - print("The \"Secure Text Entry\" alert's cancel action occurred.") + Swift.debugPrint("The \"Secure Text Entry\" alert's cancel action occurred.") } let otherAction = UIAlertAction(title: otherButtonTitle, style: .default) { _ in - print("The \"Secure Text Entry\" alert's other action occurred.") + Swift.debugPrint("The \"Secure Text Entry\" alert's other action occurred.") } /** The text field initially has no text in the text field, so we'll disable it for now. @@ -203,7 +209,7 @@ class AlertControllerViewController: UITableViewController { // Show a dialog with an "OK" and "Cancel" button. func showOkayCancelActionSheet(_ selectedIndexPath: IndexPath) { - let message = NSLocalizedString("A message should be a short, complete sentence.", comment: "") + let message = NSLocalizedString("A message needs to be a short, complete sentence.", comment: "") let cancelButtonTitle = NSLocalizedString("Cancel", comment: "") let destructiveButtonTitle = NSLocalizedString("Confirm", comment: "") @@ -211,11 +217,11 @@ class AlertControllerViewController: UITableViewController { // Create the actions. let cancelAction = UIAlertAction(title: cancelButtonTitle, style: .cancel) { _ in - print("The \"OK/Cancel\" alert action sheet's cancel action occurred.") + Swift.debugPrint("The \"OK/Cancel\" alert action sheet's cancel action occurred.") } let destructiveAction = UIAlertAction(title: destructiveButtonTitle, style: .default) { _ in - print("The \"Confirm\" alert action sheet's destructive action occurred.") + Swift.debugPrint("The \"Confirm\" alert action sheet's destructive action occurred.") } // Add the actions. @@ -238,7 +244,7 @@ class AlertControllerViewController: UITableViewController { // Show a dialog with two custom buttons. func showOtherActionSheet(_ selectedIndexPath: IndexPath) { - let message = NSLocalizedString("A message should be a short, complete sentence.", comment: "") + let message = NSLocalizedString("A message needs to be a short, complete sentence.", comment: "") let destructiveButtonTitle = NSLocalizedString("Destructive Choice", comment: "") let otherButtonTitle = NSLocalizedString("Safe Choice", comment: "") @@ -246,10 +252,10 @@ class AlertControllerViewController: UITableViewController { // Create the actions. let destructiveAction = UIAlertAction(title: destructiveButtonTitle, style: .destructive) { _ in - print("The \"Other\" alert action sheet's destructive action occurred.") + Swift.debugPrint("The \"Other\" alert action sheet's destructive action occurred.") } let otherAction = UIAlertAction(title: otherButtonTitle, style: .default) { _ in - print("The \"Other\" alert action sheet's other action occurred.") + Swift.debugPrint("The \"Other\" alert action sheet's other action occurred.") } // Add the actions. @@ -275,6 +281,7 @@ class AlertControllerViewController: UITableViewController { // MARK: - UITableViewDelegate extension AlertControllerViewController { + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { switch indexPath.section { case StyleSections.alertStyleSection.rawValue: diff --git a/UIKitCatalog/AppDelegate.swift b/UIKitCatalog/AppDelegate.swift index 6c41f8e..df13192 100755 --- a/UIKitCatalog/AppDelegate.swift +++ b/UIKitCatalog/AppDelegate.swift @@ -15,4 +15,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } - diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_background.imageset/Contents.json b/UIKitCatalog/Assets.xcassets/background.imageset/Contents.json similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_background.imageset/Contents.json rename to UIKitCatalog/Assets.xcassets/background.imageset/Contents.json diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_background.imageset/stepper_and_segment_background_1x.png b/UIKitCatalog/Assets.xcassets/background.imageset/stepper_and_segment_background_1x.png similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_background.imageset/stepper_and_segment_background_1x.png rename to UIKitCatalog/Assets.xcassets/background.imageset/stepper_and_segment_background_1x.png diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_background.imageset/stepper_and_segment_background_2x.png b/UIKitCatalog/Assets.xcassets/background.imageset/stepper_and_segment_background_2x.png similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_background.imageset/stepper_and_segment_background_2x.png rename to UIKitCatalog/Assets.xcassets/background.imageset/stepper_and_segment_background_2x.png diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_background.imageset/stepper_and_segment_background_3x.png b/UIKitCatalog/Assets.xcassets/background.imageset/stepper_and_segment_background_3x.png similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_background.imageset/stepper_and_segment_background_3x.png rename to UIKitCatalog/Assets.xcassets/background.imageset/stepper_and_segment_background_3x.png diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_background_disabled.imageset/Contents.json b/UIKitCatalog/Assets.xcassets/background_disabled.imageset/Contents.json similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_background_disabled.imageset/Contents.json rename to UIKitCatalog/Assets.xcassets/background_disabled.imageset/Contents.json diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_background_disabled.imageset/stepper_and_segment_background_disabled_1x.png b/UIKitCatalog/Assets.xcassets/background_disabled.imageset/stepper_and_segment_background_disabled_1x.png similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_background_disabled.imageset/stepper_and_segment_background_disabled_1x.png rename to UIKitCatalog/Assets.xcassets/background_disabled.imageset/stepper_and_segment_background_disabled_1x.png diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_background_disabled.imageset/stepper_and_segment_background_disabled_2x.png b/UIKitCatalog/Assets.xcassets/background_disabled.imageset/stepper_and_segment_background_disabled_2x.png similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_background_disabled.imageset/stepper_and_segment_background_disabled_2x.png rename to UIKitCatalog/Assets.xcassets/background_disabled.imageset/stepper_and_segment_background_disabled_2x.png diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_background_disabled.imageset/stepper_and_segment_background_disabled_3x.png b/UIKitCatalog/Assets.xcassets/background_disabled.imageset/stepper_and_segment_background_disabled_3x.png similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_background_disabled.imageset/stepper_and_segment_background_disabled_3x.png rename to UIKitCatalog/Assets.xcassets/background_disabled.imageset/stepper_and_segment_background_disabled_3x.png diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_background_highlighted.imageset/Contents.json b/UIKitCatalog/Assets.xcassets/background_highlighted.imageset/Contents.json similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_background_highlighted.imageset/Contents.json rename to UIKitCatalog/Assets.xcassets/background_highlighted.imageset/Contents.json diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_background_highlighted.imageset/stepper_and_segment_background_highlighted_1x.png b/UIKitCatalog/Assets.xcassets/background_highlighted.imageset/stepper_and_segment_background_highlighted_1x.png similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_background_highlighted.imageset/stepper_and_segment_background_highlighted_1x.png rename to UIKitCatalog/Assets.xcassets/background_highlighted.imageset/stepper_and_segment_background_highlighted_1x.png diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_background_highlighted.imageset/stepper_and_segment_background_highlighted_2x.png b/UIKitCatalog/Assets.xcassets/background_highlighted.imageset/stepper_and_segment_background_highlighted_2x.png similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_background_highlighted.imageset/stepper_and_segment_background_highlighted_2x.png rename to UIKitCatalog/Assets.xcassets/background_highlighted.imageset/stepper_and_segment_background_highlighted_2x.png diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_background_highlighted.imageset/stepper_and_segment_background_highlighted_3x.png b/UIKitCatalog/Assets.xcassets/background_highlighted.imageset/stepper_and_segment_background_highlighted_3x.png similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_background_highlighted.imageset/stepper_and_segment_background_highlighted_3x.png rename to UIKitCatalog/Assets.xcassets/background_highlighted.imageset/stepper_and_segment_background_highlighted_3x.png diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_segment_divider.imageset/Contents.json b/UIKitCatalog/Assets.xcassets/stepper_and_segment_divider.imageset/Contents.json old mode 100755 new mode 100644 similarity index 90% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_segment_divider.imageset/Contents.json rename to UIKitCatalog/Assets.xcassets/stepper_and_segment_divider.imageset/Contents.json index 1fdaeef..86976ae --- a/UIKitCatalog/Assets.xcassets/stepper_and_segment_segment_divider.imageset/Contents.json +++ b/UIKitCatalog/Assets.xcassets/stepper_and_segment_divider.imageset/Contents.json @@ -1,23 +1,23 @@ { "images" : [ { - "idiom" : "universal", "filename" : "stepper_and_segment_segment_divider_1x.png", + "idiom" : "universal", "scale" : "1x" }, { - "idiom" : "universal", "filename" : "stepper_and_segment_segment_divider_2x.png", + "idiom" : "universal", "scale" : "2x" }, { - "idiom" : "universal", "filename" : "stepper_and_segment_divider_3x.png", + "idiom" : "universal", "scale" : "3x" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_segment_divider.imageset/stepper_and_segment_divider_3x.png b/UIKitCatalog/Assets.xcassets/stepper_and_segment_divider.imageset/stepper_and_segment_divider_3x.png old mode 100755 new mode 100644 similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_segment_divider.imageset/stepper_and_segment_divider_3x.png rename to UIKitCatalog/Assets.xcassets/stepper_and_segment_divider.imageset/stepper_and_segment_divider_3x.png diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_segment_divider.imageset/stepper_and_segment_segment_divider_1x.png b/UIKitCatalog/Assets.xcassets/stepper_and_segment_divider.imageset/stepper_and_segment_segment_divider_1x.png old mode 100755 new mode 100644 similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_segment_divider.imageset/stepper_and_segment_segment_divider_1x.png rename to UIKitCatalog/Assets.xcassets/stepper_and_segment_divider.imageset/stepper_and_segment_segment_divider_1x.png diff --git a/UIKitCatalog/Assets.xcassets/stepper_and_segment_segment_divider.imageset/stepper_and_segment_segment_divider_2x.png b/UIKitCatalog/Assets.xcassets/stepper_and_segment_divider.imageset/stepper_and_segment_segment_divider_2x.png old mode 100755 new mode 100644 similarity index 100% rename from UIKitCatalog/Assets.xcassets/stepper_and_segment_segment_divider.imageset/stepper_and_segment_segment_divider_2x.png rename to UIKitCatalog/Assets.xcassets/stepper_and_segment_divider.imageset/stepper_and_segment_segment_divider_2x.png diff --git a/UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/Contents.json b/UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/Contents.json deleted file mode 100755 index 404bc3b..0000000 --- a/UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "decrement_1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "decrement_2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "stepper_decrement_3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/decrement_1x.png b/UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/decrement_1x.png deleted file mode 100755 index 18969fd04d23bc1611dcfd9bf7da5f2464260ecd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1068 zcmbVL&ui0A9FG*{*eD3XP?7P-5EN{Zm$qpWTf4SNUBS%J6)hfQY4WzlHF+_4v)SUc7h^ym%13>>?gi*hLUT^d!5ue_+hh4$6F4+v#CUFeLfD_kG{b=lkP*zxSpl zFT{sV3^5E7uTH2nDo3JkaDe{TR@>*OI8L+%nZ~oEWB7#8Y^_)9G-X zB!~Su9?G&Dafo7qS|ox+ml$Ef4aWKmDhf>B_K1yL5HT7}yg>3SjkKGBw{_S4t5k7(rA=RzD+O1 zXsg&fhHz0;U*t^UO%jzcs!- zytW>GU1he?FT|%$?!+F%wl;URZx3_7R+cvkuYYVWZyY}I>QHkucm3M;M>8iAE0y?Y c`dEe;6qx&uFMo_d{Zll*Rc%szS(;h?4Vq$5yZ`_I diff --git a/UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/decrement_2x.png b/UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/decrement_2x.png deleted file mode 100755 index 53d3860b0d7870742dae7ecd723d0e30725a7420..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1123 zcmbVLTSyd97#@)p6Q!UR>mjD`#lYQ}*~>UH?IycBv$^1!tV?a$gR?Wo-9cy0ICE^r z#Ynms_FzO1WEa5@0>RfV5*7qe&{G5@1lm(XP+`z(XVldm#Gqkj&N=`2zwf_Kc6Xg< ztUpvw5JY3TL(1Z{)%)sv_<#6vZw+1!A~}zG-~cKsHXz~}EC4cXC`FJ3iZ(j@8bk@A zwxs9sD4*#RRcHhhPbc6S7Df|9wB5B7br2x30E)U9qrNXLQKYWLsMC=Qld+PZq<2(o z&{OHksg*%h(5Uv~WYiTgfdP<0y2g;{h;EEp=N0kZ6VnvAu7U<*)P_^}OgEW?HXtK` zkY8n3mgKkq8;)=s+e!u*Hb^u0<@{_|jBsMGjof@FY|YjN#H^Isw1rPGs)Udw(sa38 z4wORyXcuW#5Co4S81!QezcXqg#r2y`bCp2?j%w=`(xFLuj7kBHpcsWc-3Y<3GMO!6 z)7eZEP8sbg7R?42+AzGh*0mj!1>0`ysO{uNEkI|114nEX*JGf$3dXtH-H@kggpjCW)24$IQw3GKI<~usm3PI8NgF5#+Bpb^svXc> zf(SY#XpzYtj%>*&s&0CMqh5hF;+24{p95OTh6cHwVNu_~gMUZ9fz|#eYczI-_S)mG zcBzVR4|?0J=EsMv@c|~D85<9Wc5|tS2kS;!is#(rZy(<79cLOQR!`-k{H1w*%6WXR z*|mK1eThj~H3xFn;L5RE(O>(PTc{&*ZxV$)O-%pu@tRs+L(`9$7f*)I&gqdW4}L$q zZC)2=Iwr!?nX&hMmA*$W7tY+BU$nmv7wV>_mp?9!t@YlCd>VN+IXvro*ZFg;cAJM; cdG&RFKQSGiJ%4re^HXo^({h)zkm$eo2R$ch*#H0l diff --git a/UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/stepper_decrement_3x.png b/UIKitCatalog/Assets.xcassets/stepper_decrement.imageset/stepper_decrement_3x.png deleted file mode 100755 index fa2db736ae7a1c59d1512d608183a983c8e729ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2917 zcmZ`*Rag}27X1;VOFD!@cc(PW&?zuDLrF6zA|Xl%4kC?&G&l$#8pxhx>@D25aP5pFVC!x-*@8AkKATRJ*K?7jIhgo%l$1V(c)$~f|A zl2z{ai4h-S;`+~)LOys-HlM5yUDi*jZ&Vzj>xM~MNhve*WzFSLl%){PTR$RtI=h!v zB{ajy`FsE^X`L(glM7w~a2lqnD$3JJ@)ZySj#H3=R+HRTsoT+4G~1ADI|9@zf>!^O zJ4U1^2A~<72q^-Zx&$b6mVhP5CjyQGF3v09t~hWMe6cQ`*z%0t3;B><&M^mCbD4SI# zT#n|);UEC{DU8?G9$p0EsH<=|g+vUsgZTD0;wv6!=atK~;c_2M0G9m2MlZ!A>)22# z#3--J_xyWA9``BopPt0K*V1U#gZ!;o_@U1~+2|A`V`pa8R#&GDJ0Z4C{qWFBWV=Hv z{NmB?FxB&u;~!s_`NC!F!}W+xf3y!C85iCjena-kd2T&P@4SKH>YQVor_;b0ix6R0 zF{krQ)Xqsblquwig}f2&;vc(mU0xJBBUE3M2Q?4X2|`lDOYB_;e;EnVBK%vIPXIV* z@^1SsL`sPAh*%m9xZ2ma(97osD0hQ5z5v|U=97T+*J<>S0-&8AE>fe#e$>Vy)=GT4 z?dDt?#kr%xD+pg_JA@WO?uxqY=O|q95+W2eRKp?SDEVEJL$1}_BP!L0TB6aL z%^xSyyf&fca55sTUQ&7|?%5au=Qsp!A}Mor$H+75CuTEoH(`g5ylA9Y5fwe+kA4jX2}%E2w$1E3-Y9v7=0FKy!E@2#aTu= z8lf-7J4u&BFG(8`1MO7gql<;g2x60O7<~E&8>1W39eZrSyCqhdE2q!Ko7y+)>RC&o zlqSX{+8eowUhPS1Svx`W=O3RAM7Ro5B4y^`YNu~EKL#uSHWGz=p$-ZZlTa}^j?pU&0B1=_DqRWsa z-Y|`iVb&M1#MT|g9cIvv=J}r-R+_uHC|p?U0>!vJsmi0ChRb&fd>Wd0{!FAP>w=>x zUahROKnW^qC=0K%I81ZbAX&-sJ$>tMO12?XoOu`)x4^J3ye!OKBxj73cC+&=%gD?? zld&kV&|k=ncym%lTsRVkQ$c<>M-eGS5Qx zf+p>NZ;y-jvhu#(N97rN7cT_0Z2Y+$DtQ6B@cL7HtVf9;uA}55##7>0RJ-JTy;V6R zd+ktb!ieFbla`)t$1~Nb$<{b7T^{zGTAoS%ds%Q z76})r7^@if3=IywADYaT&sCG2lG(`J$eqevY;d#*v=PIUV7|B+)UMhTS?^(*A4l3K zSr;O#Z0zfFE76twlTDRP#kY#Frm3dod6USFnxB|HOlbpa&Du0Ez6#@o33Wm`Gb0DHs_Y36Ii9^oc>RjY?xEpRD+H z|J~ItT|3><8%$x85nP-DvEEYgP^nhcHwaI@!gy0O>K<{0>X$f-h}VQ!`7S)ODy&5y z*u*pZep~;`wt5239SSS*waCz&xv5=yGlZn|V!hL!C^Wk|V{BX%1D-jP@x`>zT;Lje zw0Hlv?76hhrRAMi*J1ZD_pS8>@jP4VjIzx2OzOu}Gqazm7VmxDJ^rPGm&_az!Z$@= zpx6)JRKGO475CwWR5V~8VMnk(FonIb`Ff|R)5oKG>>YoHJWz z+eWiN_0h@gh2V2N{9iP0*cy<3I69|yLL3<}e3^2bL<7B`q`pxTk;l+0A4r?{OyS|=kwK$hhP_00q*Ezh`*Hz-8$_hET$-r=(fsVgMafsIq31)k4K36}FDkb~ wo7fiqtL_{qa(HU`e~16GS7Va^fU7G)aC3hB-FG;J%(XXQpku0C1#x`&Z$rOQivR!s diff --git a/UIKitCatalog/Assets.xcassets/stepper_increment.imageset/Contents.json b/UIKitCatalog/Assets.xcassets/stepper_increment.imageset/Contents.json deleted file mode 100755 index ac62061..0000000 --- a/UIKitCatalog/Assets.xcassets/stepper_increment.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "increment_1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "increment_2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "stepper_increment_3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/UIKitCatalog/Assets.xcassets/stepper_increment.imageset/increment_1x.png b/UIKitCatalog/Assets.xcassets/stepper_increment.imageset/increment_1x.png deleted file mode 100755 index 774f66c589b662594f0bf4ba922a525ab29ac0fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1166 zcmbVMe`wTJ9FJD37OR}UK%MRxg-!61yxd)Si9OrgCAS`$dRz~7AOmyBYcKSY7n9fB zT@e)jIz^%2@E<`D9BeS0i2o_*m<$!hVEBiOv48i^#QKM$Vo_h(UiFWvU`X=b`{w)k ze1GS3armp=o^3r0!}J!0v=UvnM9=DO`o4FwzMn2ziC!Tic$75E05PhKs|XZaa}1S` zX}69&K>ZBURddP}QZWuG7Irx^(&3t}Ptgq1KhX3|Ya9_!MPrVaVV~Z*!ve?7uwSPP z!SHja<_t{+Xk>D@Y)y_^vds=00R2sc61a$%py}4VP-$k^IbMbCBQei{ITbRVVdtHy z7)6l70Rky55w`>gfh2J-nUW;94~PO3d4b+i943{Nq=VLAts*}iOFQjpWSET|oh zrLb95%j$xfOXt&h2=zodsmbY_psOmB40Jl6CBpC4{{?C_|h;f$BP7eqXNyxtD(U87TNg#yI?NEinD|VACG(< zYkx}Cc^gUMI`w#Cd;Q?C7~EEACoA3UCs+P^`G`FGe9!-X?27$1F!lJ(gI=pxIkODz`**gh zx_wV;0<6o1o9^HEW^i@~xBOwNHZybL&tEXOph~Nb6ovD#tKf2CL(dsw@c1g@(3C(pV7ZN*#1eK qIa&Ga;Vb6e&p)E+)jz1r^0iEN_swV5-1D*M4^hyEwQJd{3jAFJg2T)jk)8oi3#0-$aN1{?c|g2d$P)DnfH z)bz|eTc!8A_bVx6rr0WloBA5~7C5J7WO`H;r3P2|g(O#HCtIc{+1n}DR9FEG$W1Lt zRH(?!$t$+1uvG$^YXxM3g!Ppaz)DK8ZIvL7itr6kaLzAERWQ{v)=f4rG*mD%(=#+N zH8V5RQ7|$vG}1Q!A~Rh>6Dw0QDv55FG|-pw6wGYnPFt43sj+7T$xvrSfQI&tPC^3CAB!YD6^m>Ge1uOWMX1cerbuV z640d(FXR^Z`oay)D~1LXFv#>P5)1SV^$hfLb3y(Bt1d1HN<}yWtLoyA%7Rq=pw#00 z(xPNwe55JEGy}s9zeOO0K-XCL7i9t?B{9b(Ke;qFHLt|e#a0O@qnDDIVr6XP=4$9- zX69;aWMF1tXlUVVXyRna}t%N=+=uFAB-e&w-_YfQ}1{rUgj{%`=3505iX zk5$E$gNym(N)74`IHi7`)DR)s@!#=hj{}d^i*%-s%2^QyJf3zvmJoN2Z8MT zzU;=lrtJS0Z*5)~dpVPFqXP4@8@EIi{@F;#+8|UJFmMFVdQ I&MBb@087D~H~;_u diff --git a/UIKitCatalog/Assets.xcassets/stepper_increment.imageset/stepper_increment_3x.png b/UIKitCatalog/Assets.xcassets/stepper_increment.imageset/stepper_increment_3x.png deleted file mode 100755 index 868bea2b69339838bf226e73d6dc3e7867bdb201..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3063 zcmZXWX*d-88pi)3`@SzJJ53n7Ft!={8b$^wON>aClr0)$i%7CJBH6bhqp@YnZiK93 z$sVHjwKU2y$9tV~o%7**xbOSh^E~(O)01$`!jOrcpB@0fWNf5oeU|WlL`!|<%SiD3 zEa?1!10eLK+Y>(0O&36AQ0ECdHM$W-uLwN6Ea31g!}@0J?`9f2OxL`YlBAG zY;bFm=J(;I@tBvUzSdl{Le}s^3}>nYM3|1%Bwn~+n#-!4O<$i%pf^9BGC3KO#APMT z_>_K%W<~f-a&%$*)BdByutM*NX42ZwY5kuJKb6p14WPr2e!5(IZQ2@u6!dw*j zsX&OsJzfuZYJke&>reFoN)B*(n0BdwDH$MVZ0BJF-Zg+W0xLr;pk)MdR`D^H0aX}q z?iCdc22Zj9r@^ir?C&eptm}|7QrWdIyqKDPq$912KaHK8>_sVpG0zoF6_;}^IXbc( zA=!KikxDGz_67mSPvtsmyLS>yV6G+*RFZMbw`4XyQIkbnU6)T+hb#Pa09Xu+7&(=O z*70LBs4+gLZ^U<~Ja01OKOiM~)UxQ*gZzydyFI^u*ytBL$4^hMuB=R%bio~6`t8C` z(H*zi>`w0fj({DL4!*T6iA5?nMH*5ae(M+D*J;c){4`B-pFq|4Y9 zkAiS6Te15m>*XZvDHMq$z+YVcEIvxUy|f^GM5(o)4BmNZQG}(+lsdUl{xX$hMFqAk zkpS3j@@@YrMN5hCj9wfLBLCDrG0YbS7!TtY{s7$66O%>u*J=0A0-%>433;b0u;0!r z-9~+({rqe@!?Cl<6S!Dc2b>j7e;acl!1;1z6kIBH=$#LW zBeSL-%O4loymqPPNIELrURn+p;hA^}*QY4aWLlo=m_y-qBP#YpF`>SAPAd`9RAnQ1 z>!(N|TO+|;&46=iaW@PaQ#E>lLez0=v!VRU$XYASuZ;Bwe5oq`6N5JlUv3A==cI7w z=Y9$Jz^|F4Qn3D|!joktMGMg**Y=&4MzQOzoNRX|{QXsFx-_?e_ac>y72W*ru2)hv zB^HA0!8ET)V~g)WFGvCYt_(osIK>W~ykD>eNf(a83@ zi`P{_J`QChEjq!T!U1Isi$`>+im@jk6eRJ_&l$geiyUPiH5k2TExI9HmaAmMFPhdj zbKAR?M(w4v(3Rep73@mqisFjEitw%@J=Rsn|Lvj8l9YA7mkHl0>#EW!wR@I=u5DqC z^;~HIl1(|q0bW&rv#zm3?e%rADM z0~?0lGrv>r2Qf0maz*#Vqow$Ad1-jjd_lZ5S!Tt2V~Kn{NI6yct*rHImTX^J6a)u( z#Fg4lpKg*akuCs%BFoB)%je4(Z1-#jkSS$1ZNjS!Y!z*m%jmvlRM?hduWi`~*r3Za zO5@7WrM^h*w-NRy3DowTW}TLZw^sS@Z!I@>3o*Fy)dfondoxwUK8RHAl6XHf{pca2 zDeFYADN(b$tUwK+XrgFWXTA5*Lz`wf%m46IpatE!R7vJu#M61spO=>|3luAv;pN?L zc$a5nW}qsJDy%9Dw^~%Hw@a|aE&5INN0H3BJtmSjBn{Cig!9;Sn`xW568aKub4~Sw zYT0t{B7uS?y`WF`N_MjHT5n?V%zR7cLt57VTnLArAWwY$lpGi`qNwW_#i)sl1YX!@ zWq)6oAhd`0w-v&vQwLGxlJ1hZ_#_oS0xKLhcxJ$obf^2qve>duw@-JW1hs^)gsJ(a z`A&|tIllOEv4)w3S;Cr_^MNO>6`QXA` z(5^h_hR@ZVJ83Ox;f>+XPBdwl;-;A^1RVq;?tcj2RKKo1pF?=HqaFTvKVt)mMh&wL zPbn_rI0i0v-ND-Ajv1nP(D?h&d+~`0rcnd3nOd^B0^M`HEh{F02mVfrp`!=b+`8P3 z?NVaRj>^uRow(W8gG#S2=h-XX|I*ou_-3^_qRsF+UCw)5^l(3{Ui%Ui0|$e1WY zw8~|@U^A+XN{!mxvL5z><0vf3_^#;%HCaft%Dua-3&-1gaZA`GhUGAxENHKjUq}I= zV?U;ZyG)i{T0KL@EZ*Yk%fED4A*I|&;=3F{;>q+O9fBQb5$K4@Nc?VX5|pE&x%FTtN169WzU<9t!y=XsWl!N54pY?hh{)Hc!K$H_>Yz z4+kpUV`@)7-%D(OUQDOy^R9bddEQ=z9tSV}j&zWkgwWKY-R(R+4JGueWmM!{8QLDK z>nA=}ef9G-d7Hh1eengZDAv?YMi8~uQt72u3-b>l(l2wJzY=?$x)S#BDGuT@ZdtKy zmsuUrA`xot9eK08KdQZ+!h4IsmVPxRd~0@c+sP6IwO^=r`4fv3(BevXTFpu1$>e%G zB{dtoDiG(}|0#R+<%hD0F8u9bk5P|}wRxF5N9K(3%(YDBdrZ?aAD9-df7m|wrB8%r z4oMN4qHzd(;V0O~X7`dl!jOhG@-1>7`3I@8GdfrAGI@A!WJe&Z`Ipjey&Gs5LUNIUjm#NW8@IN(p8kVzP(qyEC&9HI|0n9ZtfgW5Xk4f6qD--{8X5 z-3SX_EWSS*SpyeG^~s6r8qFGn8@nF^cWa5O9Cjb=a%$9X`D_Mm!cNo1EPI+>HRV*} zZ+PQ(LR;#?JV+Y?d8&lD{g#lwxycjbd1{(-EqkH9q|c;-1@5_s0Lc@=K+|-PdicU7$w)kp@-5uQ* zTl<0)d+^;@Q6` zQ)kME0HpA7(@5HZ%%MF!9OAj*4n%5ir4 zPk+rXh|PTetsU{`4P%QA^K;6BbAkbJ^PRj(yz0N0lt-^sN@rso#JuFmEX{qeJ%pu^ zimYo=EUyh5P_gO&ivJjC&}Ju!fLCQxlYVA3exHczF-IV6>v`Dh`4!E_@+O?0fpfG% znE%<-&Fhysk6Itg4D5>6J3CDdL6bL!#bG#+Rz))baQ zL^K?tmH#~>o?KtBJFnqpmh14v;plFaQ>6g>kWTnf$;4mtP2-MOUbhMF3q2{pqLcFW xuM_`&HxO~8KizdR`0w>+J-R3ZC;%XnDM8IwYB!0us*`7Afw8`YUNzh~>R;U>ogDxG diff --git a/UIKitCatalog/Assets.xcassets/x_icon.imageset/Contents.json b/UIKitCatalog/Assets.xcassets/x_icon.imageset/Contents.json deleted file mode 100755 index 90ff9da..0000000 --- a/UIKitCatalog/Assets.xcassets/x_icon.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "x_icon_1x.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "x_icon_2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "x_icon_3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/UIKitCatalog/Assets.xcassets/x_icon.imageset/x_icon_1x.png b/UIKitCatalog/Assets.xcassets/x_icon.imageset/x_icon_1x.png deleted file mode 100755 index 84b67200bed7432a5bb922f6211d9bbc255dfffd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1424 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m@_g%B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`kBtHuNWFoz#!AFNG#Ad)HBe}%?0@jth%@)C>7xhtg4GcDhpEegHnt0 zON)|$@sXws(+mtd{1$-}0$pR}Uz7=ql*AmD{N&Qy)VvZ;7h5Huj9yA+ij}dEo2#LV znVGAxk%5_op`nGdp^1~Zlevk3rG=}dfg{WeY^2lw4p!z%&SA!V^D`1J6FGdB8kV1k4=Gwvt&442=6cT^vIyZf%*q zU!TQMcQ`ereU}%|ni}M&e9*PTedXkgcm4|${2uo?FJ2bh;q^3LabkwW zef9iF(>G^7Kl{$-)6U&H|9;&0z3kap-?ex1+%tp|IPbV7-nNloKF_9q`GJT|vtWU= z$E*)~J@@!MV%>0veZ7P17wu`w9x$FMs5gD~C9g?;TG_#{qZ99OXR^yky}QGux%a!n z+NKl1)6NzBmw9uEUD=i+PaZJ!R>XZo8?%sNt; z`JZLIT~M!u_S-A<%#+u>)Scz$`ib`g-?2!q{)ZAqRu zWk}?HlXhJabnBCbk=DlSLJch= zt-?h*&8u$7X;0fW?XgXHRFLn?rEAYj$~vW!n|etu`@f~e>Fm5)zDFnSTJqPtNJzhF zHp8VoTjDR}9V*zF{lPOmLI1$Fqvt-$+23R9oS)&%c<2L}KE diff --git a/UIKitCatalog/Assets.xcassets/x_icon.imageset/x_icon_2x.png b/UIKitCatalog/Assets.xcassets/x_icon.imageset/x_icon_2x.png deleted file mode 100755 index 71c09f4e0b23a16bdab7ca26250fb7b209f5c9d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1746 zcmbVNX;2eq7>*zyDAu;XXoR{1ty;{nA?Mb#latFJ6JkXz>bkqY4kR0r#f0DmQ?J@6 z=!_RA8kK>ncp#;WW~BU3Y^fpx0R({#3I-@l6-6u9Za8dzICiExyZe3LyYKV7_w3&I zxJZ9Lx*v^3^N)^FXsC6$`=#3l@g*1;*527}S8 zj5`>G@Jz+EiDaUB1%wbf7VOqxS#$;pO_6bJyt(`2Hkl(8+afeo@aY@N;>*SNNc)L{R(@lI`%Hp_sq zHJFLWG$K?zlEbFJRPNqy$gN0u1FbaTR8in`1%YJhFg+QqkT9q(EEGqf2(C&ADg**$ z1eYTafuKkZ@?=7pkjD{=lwyu_isM~anJPjgQ;GN@6<-B{$_NopAr{FwN~shSh-G5g z6gFCKB4Is(P1(gMyOUVnd$Ew*h`}Ub)DlGcR0qVT5+q?tB@BQ(K>#dK!w9Z-3zoVI zG!d@?Gvb>uRAnS|z<7os{2d zu5E@z-RtKFb6LR^&IgX5z|r|-7rX316)k<)oSc5_VEdNB6%C$4$vv~{ztR zIyd|4)VeBDjbE@GynWX<+%U^_sGrHL7nOD}KDMc?_ii<=L(g6*vwzaRr;hi7D`d-s zFmB_~%ojm9^m6@=8BdrVyyQ;9%=+uSo`nN1!_IZsX|}Dy&NpXcL^bvK1v7Pw98R1wZlDlJjXl34Q>S!1Gw*+6c5B^qE zYwPPgcCpVETi;SVA{3u`rg8oCrlex;GGIae5idNw;q?veE1UI?OH}^{hQ!Y73_NXi P|7D_;af&k8#@xRFQys5` diff --git a/UIKitCatalog/Assets.xcassets/x_icon.imageset/x_icon_3x.png b/UIKitCatalog/Assets.xcassets/x_icon.imageset/x_icon_3x.png deleted file mode 100755 index 968331d6355bc6b5138f47aaeb86fbe1a470a802..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3419 zcmV-h4W#mkP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0007pNkl__uF_TBYUEcXJq?H_Q7JYz{(NuMBO_9N74Z|RqY+*F*m|!E+hG8Sv7>7+eF#gw z5KOLt&15#40o(&q;A2nYQ()4zs|jHVTml$~8ZkyA*bp^gI3etUr~$(np%0>D3@?Oz z5G7)GBMd>5gb{==24XBm1N<%(VJt+>7$OKR5IJIqA-F>1gdvLH17R@UB*YL$@Pz=l zON60<5CV|`Lk%GmA|{3^f&@ei40Qx42u+MA2$B#Q7|{@ifrwpWRK@Ux7(%#Gx(igp@PSB$po-xNks3h_!v!K21Qm?25V;|U zV~l~w6+skZ2qJfcE=JG_;e8OLAeaO#m+pfo4Ivm~7euKDK^Qhf=?LB!U5Hu`yf6$z zZ3r$HQ{cV<4uP`@5lrUoiSEl8?F+CQ;1lptA;J*GGw>PMn3=Bi4vbn5`Y;T_8n&nW xx)`+~*ce*~@OsK`4ZNrB4{6MS8@rGH1_0D$9CWmFg?a!0002ovPDHLkV1j^bGKK&E diff --git a/UIKitCatalog/Base.lproj/ActivityIndicatorViewController.storyboard b/UIKitCatalog/Base.lproj/ActivityIndicatorViewController.storyboard index ec8a11b..40c0d74 100755 --- a/UIKitCatalog/Base.lproj/ActivityIndicatorViewController.storyboard +++ b/UIKitCatalog/Base.lproj/ActivityIndicatorViewController.storyboard @@ -1,8 +1,10 @@ - - + + - + + + @@ -10,105 +12,104 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + - - - - - + + + + + + + + + + + - - - - - - + + + + + diff --git a/UIKitCatalog/Base.lproj/ButtonViewController.storyboard b/UIKitCatalog/Base.lproj/ButtonViewController.storyboard index 0009829..0076e70 100755 --- a/UIKitCatalog/Base.lproj/ButtonViewController.storyboard +++ b/UIKitCatalog/Base.lproj/ButtonViewController.storyboard @@ -1,8 +1,10 @@ - + - + + + @@ -10,184 +12,427 @@ - + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -195,16 +440,6 @@ - - - - - - - - - - @@ -212,8 +447,8 @@ - - + + diff --git a/UIKitCatalog/Base.lproj/Credits.rtf b/UIKitCatalog/Base.lproj/Credits.rtf index ce687fd..c9f3ebb 100755 --- a/UIKitCatalog/Base.lproj/Credits.rtf +++ b/UIKitCatalog/Base.lproj/Credits.rtf @@ -1,9 +1,10 @@ -{\rtf1\ansi\ansicpg1252\cocoartf2513 +{\rtf1\ansi\ansicpg1252\cocoartf2617 \cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 LucidaGrande;} {\colortbl;\red255\green255\blue255;\red0\green0\blue0;} {\*\expandedcolortbl;;\cssrgb\c0\c0\c0\cname textColor;} \vieww9000\viewh8400\viewkind0 \pard\tx960\tx1920\tx2880\tx3840\tx4800\tx5760\tx6720\tx7680\tx8640\tx9600\qc\partightenfactor0 -\f0\fs20 \cf2 Demonstrates how to use UIKit's views, controls and pickers.\ +\f0\fs20 \cf2 Demonstrates how to use {\field{\*\fldinst{HYPERLINK "https://developer.apple.com/documentation/uikit"}}{\fldrslt UIKit}}\ +views, controls and pickers.\ } \ No newline at end of file diff --git a/UIKitCatalog/Base.lproj/ImageViewController.storyboard b/UIKitCatalog/Base.lproj/ImageViewController.storyboard index 4a3f018..886c071 100755 --- a/UIKitCatalog/Base.lproj/ImageViewController.storyboard +++ b/UIKitCatalog/Base.lproj/ImageViewController.storyboard @@ -1,8 +1,10 @@ - + - + + + @@ -13,7 +15,7 @@ - + @@ -25,4 +27,9 @@ + + + + + diff --git a/UIKitCatalog/Base.lproj/InfoPlist.strings b/UIKitCatalog/Base.lproj/InfoPlist.strings index 25c271e..693600d 100755 --- a/UIKitCatalog/Base.lproj/InfoPlist.strings +++ b/UIKitCatalog/Base.lproj/InfoPlist.strings @@ -5,5 +5,5 @@ Abstract: Localized versions of Info.plist keys. */ -CFBundleGetInfoString = "v16.0, Copyright 2008-2020, Apple Inc."; -NSHumanReadableCopyright = "Copyright © 2008-2020, Apple Inc."; +CFBundleGetInfoString = "v16.0, Copyright 2008-2021, Apple Inc."; +NSHumanReadableCopyright = "Copyright © 2008-2021, Apple Inc."; diff --git a/UIKitCatalog/Base.lproj/Localizable.strings b/UIKitCatalog/Base.lproj/Localizable.strings index 162abdc..78c0466 100755 --- a/UIKitCatalog/Base.lproj/Localizable.strings +++ b/UIKitCatalog/Base.lproj/Localizable.strings @@ -11,10 +11,11 @@ Strings used across the application via the NSLocalizedString API. "Destructive Choice" = "Destructive Choice"; "Safe Choice" = "Safe Choice"; "A Short Title Is Best" = "A Short Title Is Best"; -"A message should be a short, complete sentence." = "A message should be a short, complete sentence."; +"A message needs to be a short, complete sentence." = "A message needs to be a short, complete sentence."; "Choice One" = "Choice One"; "Choice Two" = "Choice Two"; "Button" = "Button"; +"Pressed" = "Pressed"; "X Button" = "X Button"; "Image" = "Image"; "bold" = "bold"; @@ -26,7 +27,7 @@ Strings used across the application via the NSLocalizedString API. "Red color component value" = "Red color component value"; "Green color component value" = "Green color component value"; "Blue color component value" = "Blue color component value"; -"Animated images" = "Animated images"; +"Animated" = "A slide show of images"; "Airplane" = "Airplane"; "Gift" = "Gift"; @@ -35,6 +36,8 @@ Strings used across the application via the NSLocalizedString API. "An error occurred:" = "An error occurred:"; "ButtonsTitle" = "Buttons"; +"MenuButtonsTitle" = "Menu Buttons"; +"PointerInteractionButtonsTitle" = "Pointer Interaction"; "PageControlTitle" = "Page Controls"; "SearchBarsTitle" = "Search Bars"; "SegmentedControlsTitle" = "Segmented Controls"; @@ -45,11 +48,16 @@ Strings used across the application via the NSLocalizedString API. "ActivityIndicatorsTitle" = "Activity Indicators"; "AlertControllersTitle" = "Alert Controllers"; + +"ImagesTitle" = "Image Views"; "ImageViewTitle" = "Image View"; +"SymbolsTitle" = "SF Symbol"; + "ProgressViewsTitle" = "Progress Views"; "StackViewsTitle" = "Stack Views"; "TextViewTitle" = "Text View"; "ToolbarsTitle" = "Toolbars"; +"VisualEffectTitle" = "Visual Effect"; "WebViewTitle" = "Web View"; "DatePickerTitle" = "Date Picker"; @@ -79,3 +87,87 @@ Strings used across the application via the NSLocalizedString API. "SwitchTitle" = "Title"; +"DefaultSwitchTitle" = "Default"; +"CheckboxSwitchTitle" = "Checkbox"; +"TintedSwitchTitle" = "Tinted"; + +"ImageToolTipTitle" = "This is a list of flower photos obtained from the sample's asset library."; +"GrayStyleButtonToolTipTitle" = "This is a gray-style system button."; +"TintedStyleButtonToolTipTitle" = "This is a tinted-style system button."; +"FilledStyleButtonToolTipTitle" = "This is a filled-style system button."; +"CapsuleStyleButtonToolTipTitle" = "This is a capsule-style system button."; +"CartFilledButtonToolTipTitle" = "Button cart is filled"; +"CartEmptyButtonToolTipTitle" = "Button cart is empty"; +"XButtonToolTipTitle" = "X Button"; +"PersonButtonToolTipTitle" = "Person Button"; +"VisualEffectToolTipTitle" = "This demonstrates how to use a UIVisualEffectView on top of an UIImageView and underneath a UITextView."; + +"VisualEffectTextContent" = "This is a UITextView with text content placed inside a UIVisualEffectView. This is a UITextView with text content placed inside a UIVisualEffectView. This is a UITextView with text content placed inside a UIVisualEffectView."; + +"DefaultTitle" = "Default"; +"DetailDisclosureTitle" = "Detail Disclosure"; +"AddContactTitle" = "Add Contact"; +"CloseTitle" = "Close"; +"GrayTitle" = "Gray"; +"TintedTitle" = "Tinted"; +"FilledTitle" = "Filled"; +"CornerStyleTitle" = "Corner Style"; +"ToggleTitle" = "Toggle"; +"ButtonColorTitle" = "Colored Title"; + +"ImageTitle" = "Image"; +"AttributedStringTitle" = "Attributed String"; +"SymbolTitle" = "Symbol"; + +"LargeSymbolTitle" = "Large Symbol"; +"SymbolStringTitle" = "Symbol + String"; +"StringSymbolTitle" = "String + Symbol"; +"MultiTitleTitle" = "Multi-Title"; +"BackgroundTitle" = "Background"; + +"UpdateActivityHandlerTitle" = "Update Activity Handler"; +"UpdateHandlerTitle" = "Update Handler"; +"UpdateImageHandlerTitle" = "Update Handler (Button Image)"; + +"AddToCartTitle" = "Add to Cart"; + +"DropDownTitle" = "Drop Down"; +"DropDownProgTitle" = "Drop Down Programmatic"; +"DropDownMultiActionTitle" = "Drop Down Multi-Action"; +"DropDownButtonSubMenuTitle" = "Drop Down Submenu"; +"PopupSelection" = "Popup Selection"; +"PopupMenuTitle" = "Popup Menu"; + +"CustomSegmentsTitle" = "Custom Segments"; +"CustomBackgroundTitle" = "Custom Background"; +"ActionBasedTitle" = "Action Based"; + +"CustomTitle" = "Custom"; +"MinMaxImagesTitle" = "Min and Max Images"; + +"DefaultStepperTitle" = "Default Stepper"; +"TintedStepperTitle" = "Tinted Stepper"; +"CustomStepperTitle" = "Custom Stepper"; + +"PlainSymbolTitle" = "Default"; +"TintedSymbolTitle" = "Tinted"; +"LargeSymbolTitle" = "Large"; +"HierarchicalSymbolTitle" = "Hierarchical Color"; +"PaletteSymbolTitle" = "Palette Color"; +"PreferringMultiColorSymbolTitle" = "Preferring Multi-Color"; + +"DefaultTextFieldTitle" = "Default"; +"TintedTextFieldTitle" = "Tinted"; +"SecuretTextFieldTitle" = "Secure"; +"SpecificKeyboardTextFieldTitle" = "Specific Keyboard"; +"CustomTextFieldTitle" = "Custom"; +"SearchTextFieldTitle" = "Search"; + +"MediumIndicatorTitle" = "Medium"; +"LargeIndicatorTitle" = "Large"; +"MediumTintedIndicatorTitle" = "Medium Tinted"; +"LargeTintedIndicatorTitle" = "Large Tinted"; + +"ProgressDefaultTitle" = "Default"; +"ProgressBarTitle" = "Bar"; +"ProgressTintedTitle" = "Tinted"; diff --git a/UIKitCatalog/Base.lproj/Main.storyboard b/UIKitCatalog/Base.lproj/Main.storyboard index 3c61f95..afda7f7 100755 --- a/UIKitCatalog/Base.lproj/Main.storyboard +++ b/UIKitCatalog/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -33,7 +33,7 @@ @@ -86,7 +86,7 @@ - + diff --git a/UIKitCatalog/Base.lproj/MenuButtonViewController.storyboard b/UIKitCatalog/Base.lproj/MenuButtonViewController.storyboard new file mode 100755 index 0000000..6e7ecf3 --- /dev/null +++ b/UIKitCatalog/Base.lproj/MenuButtonViewController.storyboard @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UIKitCatalog/Base.lproj/PointerInteractionButtonViewController.storyboard b/UIKitCatalog/Base.lproj/PointerInteractionButtonViewController.storyboard new file mode 100755 index 0000000..664719d --- /dev/null +++ b/UIKitCatalog/Base.lproj/PointerInteractionButtonViewController.storyboard @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UIKitCatalog/Base.lproj/ProgressViewController.storyboard b/UIKitCatalog/Base.lproj/ProgressViewController.storyboard index 61a47fb..efc6420 100755 --- a/UIKitCatalog/Base.lproj/ProgressViewController.storyboard +++ b/UIKitCatalog/Base.lproj/ProgressViewController.storyboard @@ -1,8 +1,10 @@ - - + + - + + + @@ -10,83 +12,70 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -94,18 +83,15 @@ - - - - - - - - + + + + + diff --git a/UIKitCatalog/Base.lproj/SegmentedControlViewController.storyboard b/UIKitCatalog/Base.lproj/SegmentedControlViewController.storyboard index 67ae311..4166c5b 100755 --- a/UIKitCatalog/Base.lproj/SegmentedControlViewController.storyboard +++ b/UIKitCatalog/Base.lproj/SegmentedControlViewController.storyboard @@ -1,8 +1,10 @@ - + - + + + @@ -10,157 +12,134 @@ - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - + - - - - - + + + + + + + + + + + + + + + @@ -168,22 +147,15 @@ - - - - - - - - + - - + + diff --git a/UIKitCatalog/Base.lproj/SliderViewController.storyboard b/UIKitCatalog/Base.lproj/SliderViewController.storyboard index ad4b964..4420a0f 100755 --- a/UIKitCatalog/Base.lproj/SliderViewController.storyboard +++ b/UIKitCatalog/Base.lproj/SliderViewController.storyboard @@ -1,8 +1,10 @@ - + - + + + @@ -10,104 +12,89 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -115,16 +102,15 @@ - - - - - - + + + + + diff --git a/UIKitCatalog/Base.lproj/StepperViewController.storyboard b/UIKitCatalog/Base.lproj/StepperViewController.storyboard index 38f46b5..a28bc8f 100755 --- a/UIKitCatalog/Base.lproj/StepperViewController.storyboard +++ b/UIKitCatalog/Base.lproj/StepperViewController.storyboard @@ -1,8 +1,10 @@ - - + + - + + + @@ -10,102 +12,79 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -113,18 +92,15 @@ - - - - - - - - + + + + + diff --git a/UIKitCatalog/Base.lproj/SwitchViewController.storyboard b/UIKitCatalog/Base.lproj/SwitchViewController.storyboard index 5ebd925..69655b0 100755 --- a/UIKitCatalog/Base.lproj/SwitchViewController.storyboard +++ b/UIKitCatalog/Base.lproj/SwitchViewController.storyboard @@ -1,8 +1,10 @@ - + - + + + @@ -10,75 +12,64 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -86,11 +77,6 @@ - - - - - @@ -98,8 +84,8 @@ - - + + diff --git a/UIKitCatalog/Base.lproj/SymbolViewController.storyboard b/UIKitCatalog/Base.lproj/SymbolViewController.storyboard new file mode 100755 index 0000000..cecdae8 --- /dev/null +++ b/UIKitCatalog/Base.lproj/SymbolViewController.storyboard @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UIKitCatalog/Base.lproj/TextFieldViewController.storyboard b/UIKitCatalog/Base.lproj/TextFieldViewController.storyboard index 29ea6d5..3e2676b 100755 --- a/UIKitCatalog/Base.lproj/TextFieldViewController.storyboard +++ b/UIKitCatalog/Base.lproj/TextFieldViewController.storyboard @@ -1,8 +1,10 @@ - + - + + + @@ -10,180 +12,144 @@ - + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + @@ -191,18 +157,15 @@ - - - - - - - - + + + + + diff --git a/UIKitCatalog/Base.lproj/VisualEffectViewController.storyboard b/UIKitCatalog/Base.lproj/VisualEffectViewController.storyboard new file mode 100755 index 0000000..12d43a5 --- /dev/null +++ b/UIKitCatalog/Base.lproj/VisualEffectViewController.storyboard @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UIKitCatalog/Base.lproj/content.html b/UIKitCatalog/Base.lproj/content.html index 5626a38..c2dc899 100755 --- a/UIKitCatalog/Base.lproj/content.html +++ b/UIKitCatalog/Base.lproj/content.html @@ -10,6 +10,7 @@
-

This is HTML content inside a WKWebView.

+

This is HTML content inside a WKWebView.

+ For more information refer to developer.apple.com diff --git a/UIKitCatalog/BaseTableViewController.swift b/UIKitCatalog/BaseTableViewController.swift new file mode 100644 index 0000000..9320cdd --- /dev/null +++ b/UIKitCatalog/BaseTableViewController.swift @@ -0,0 +1,52 @@ +/* +See LICENSE folder for this sample’s licensing information. + +Abstract: +A base class used for all UITableViewControllers in this sample app. +*/ + +import UIKit + +class BaseTableViewController: UITableViewController { + // List of table view cell test cases. + var testCells = [CaseElement]() + + func centeredHeaderView(_ title: String) -> UITableViewHeaderFooterView { + // Set the header title and make it centered. + let headerView: UITableViewHeaderFooterView = UITableViewHeaderFooterView() + var content = UIListContentConfiguration.groupedHeader() + content.text = title + content.textProperties.alignment = .center + headerView.contentConfiguration = content + return headerView + } + + // MARK: - UITableViewDataSource + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + return centeredHeaderView(testCells[section].title) + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + return testCells[section].title + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 1 + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return testCells.count + } + + override func tableView(_ tableView: UITableView, + cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cellTest = testCells[indexPath.section] + let cell = tableView.dequeueReusableCell(withIdentifier: cellTest.cellID, for: indexPath) + if let view = cellTest.targetView(cell) { + cellTest.configHandler(view) + } + return cell + } + +} diff --git a/UIKitCatalog/ButtonViewController+Configs.swift b/UIKitCatalog/ButtonViewController+Configs.swift new file mode 100755 index 0000000..f711860 --- /dev/null +++ b/UIKitCatalog/ButtonViewController+Configs.swift @@ -0,0 +1,468 @@ +/* +See LICENSE folder for this sample’s licensing information. + +Abstract: +Configuration functions for all the UIButtons found in ButtonViewController. +*/ + +import UIKit + +extension ButtonViewController: UIToolTipInteractionDelegate { + + func configureSystemTextButton(_ button: UIButton) { + button.setTitle(NSLocalizedString("Button", comment: ""), for: []) + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + func configureSystemDetailDisclosureButton(_ button: UIButton) { + // Nothing particular to set here, it's all been done in the storyboard. + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + func configureSystemContactAddButton(_ button: UIButton) { + // Nothing particular to set here, it's all been done in the storyboard. + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + func configureCloseButton(_ button: UIButton) { + // Nothing particular to set here, it's all been done in the storyboard. + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + @available(iOS 15.0, *) + func configureStyleGrayButton(_ button: UIButton) { + // Note this can be also be done in the storyboard for this button. + let config = UIButton.Configuration.gray() + button.configuration = config + + button.setTitle(NSLocalizedString("Button", comment: ""), for: .normal) + button.toolTip = NSLocalizedString("GrayStyleButtonToolTipTitle", comment: "") + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + @available(iOS 15.0, *) + func configureStyleTintedButton(_ button: UIButton) { + // Note this can be also be done in the storyboard for this button. + + var config = UIButton.Configuration.tinted() + + /** To keep the look the same betwen iOS and macOS: + For tinted color to work in Mac Catalyst, use UIBehavioralStyle as ".pad", + Available in macOS 12 or later (Mac Catalyst 15.0 or later). + Use this for controls that need to look the same between iOS and macOS. + */ + if traitCollection.userInterfaceIdiom == .mac { + button.preferredBehavioralStyle = .pad + } + + // The following will make the button title red and background a lighter red. + config.baseBackgroundColor = .systemRed + config.baseForegroundColor = .systemRed + + button.setTitle(NSLocalizedString("Button", comment: ""), for: .normal) + button.toolTip = NSLocalizedString("TintedStyleButtonToolTipTitle", comment: "") + + button.configuration = config + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + @available(iOS 15.0, *) + func configureStyleFilledButton(_ button: UIButton) { + // Note this can be also be done in the storyboard for this button. + var config = UIButton.Configuration.filled() + config.background.backgroundColor = .systemRed + button.configuration = config + + button.setTitle(NSLocalizedString("Button", comment: ""), for: .normal) + button.toolTip = NSLocalizedString("FilledStyleButtonToolTipTitle", comment: "") + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + @available(iOS 15.0, *) + func configureCornerStyleButton(_ button: UIButton) { + /** To keep the look the same betwen iOS and macOS: + For cornerStyle to work in Mac Catalyst, use UIBehavioralStyle as ".pad", + Available in macOS 12 or later (Mac Catalyst 15.0 or later). + Use this for controls that need to look the same between iOS and macOS. + */ + if traitCollection.userInterfaceIdiom == .mac { + button.preferredBehavioralStyle = .pad + } + + var config = UIButton.Configuration.gray() + config.cornerStyle = .capsule + button.configuration = config + + button.setTitle(NSLocalizedString("Button", comment: ""), for: .normal) + button.toolTip = NSLocalizedString("CapsuleStyleButtonToolTipTitle", comment: "") + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + func configureImageButton(_ button: UIButton) { + // To create this button in code you can use `UIButton.init(type: .system)`. + + // Set the tint color to the button's image. + if let image = UIImage(systemName: "xmark") { + let imageButtonNormalImage = image.withTintColor(.systemPurple) + button.setImage(imageButtonNormalImage, for: .normal) + } + + // Since this button title is just an image, add an accessibility label. + button.accessibilityLabel = NSLocalizedString("X", comment: "") + + if #available(iOS 15, *) { + button.toolTip = NSLocalizedString("XButtonToolTipTitle", comment: "") + } + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + func configureAttributedTextSystemButton(_ button: UIButton) { + let buttonTitle = NSLocalizedString("Button", comment: "") + + // Set the button's title for normal state. + let normalTitleAttributes: [NSAttributedString.Key: Any] = [ + NSAttributedString.Key.strikethroughStyle: NSUnderlineStyle.single.rawValue + ] + + let normalAttributedTitle = NSAttributedString(string: buttonTitle, attributes: normalTitleAttributes) + button.setAttributedTitle(normalAttributedTitle, for: .normal) + + // Set the button's title for highlighted state (note this is not supported in Mac Catalyst). + let highlightedTitleAttributes: [NSAttributedString.Key: Any] = [ + NSAttributedString.Key.foregroundColor: UIColor.systemGreen, + NSAttributedString.Key.strikethroughStyle: NSUnderlineStyle.thick.rawValue + ] + let highlightedAttributedTitle = NSAttributedString(string: buttonTitle, attributes: highlightedTitleAttributes) + button.setAttributedTitle(highlightedAttributedTitle, for: .highlighted) + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + func configureSymbolButton(_ button: UIButton) { + let buttonImage = UIImage(systemName: "person") + + if #available(iOS 15, *) { + // For iOS 15 use the UIButtonConfiguration to set the image. + var buttonConfig = UIButton.Configuration.plain() + buttonConfig.image = buttonImage + button.configuration = buttonConfig + + button.toolTip = NSLocalizedString("PersonButtonToolTipTitle", comment: "") + } else { + button.setImage(buttonImage, for: .normal) + } + + let config = UIImage.SymbolConfiguration(textStyle: .body, scale: .large) + button.setPreferredSymbolConfiguration(config, forImageIn: .normal) + + // Since this button title is just an image, add an accessibility label. + button.accessibilityLabel = NSLocalizedString("Person", comment: "") + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + func configureLargeSymbolButton(_ button: UIButton) { + let buttonImage = UIImage(systemName: "person") + + if #available(iOS 15, *) { + // For iOS 15 use the UIButtonConfiguration to change the size. + var buttonConfig = UIButton.Configuration.plain() + buttonConfig.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(textStyle: .largeTitle) + buttonConfig.image = buttonImage + button.configuration = buttonConfig + } else { + button.setImage(buttonImage, for: .normal) + } + + // Since this button title is just an image, add an accessibility label. + button.accessibilityLabel = NSLocalizedString("Person", comment: "") + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + func configureSymbolTextButton(_ button: UIButton) { + // Button with image to the left of the title. + + let buttonImage = UIImage(systemName: "person") + + if #available(iOS 15, *) { + // Use UIButtonConfiguration to set the image. + var buttonConfig = UIButton.Configuration.plain() + + // Set up the symbol image size to match that of the title font size. + buttonConfig.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(textStyle: .body) + buttonConfig.image = buttonImage + + button.configuration = buttonConfig + } else { + button.setImage(buttonImage, for: .normal) + + // Set up the symbol image size to match that of the title font size. + let config = UIImage.SymbolConfiguration(textStyle: .body, scale: .small) + button.setPreferredSymbolConfiguration(config, forImageIn: .normal) + } + + // Set the button's title and font. + button.setTitle(NSLocalizedString("Person", comment: ""), for: []) + button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body) + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + func configureTextSymbolButton(_ button: UIButton) { + // Button with image to the right of the title. + + let buttonImage = UIImage(systemName: "person") + + if #available(iOS 15, *) { + // Use UIButtonConfiguration to set the image. + var buttonConfig = UIButton.Configuration.plain() + + // Set up the symbol image size to match that of the title font size. + buttonConfig.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(textStyle: .body) + + buttonConfig.image = buttonImage + + // Set the image placement to the right of the title. + /** For image placement to work in Mac Catalyst, use UIBehavioralStyle as ".pad", + Available in macOS 12 or later (Mac Catalyst 15.0 or later). + Use this for controls that need to look the same between iOS and macOS. + */ + if traitCollection.userInterfaceIdiom == .mac { + button.preferredBehavioralStyle = .pad + } + buttonConfig.imagePlacement = .trailing + + button.configuration = buttonConfig + } + + // Set the button's title and font. + button.setTitle(NSLocalizedString("Person", comment: ""), for: []) + button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body) + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + @available(iOS 15.0, *) + func configureMultiTitleButton(_ button: UIButton) { + /** To keep the look the same betwen iOS and macOS: + For setTitle(.highlighted) to work in Mac Catalyst, use UIBehavioralStyle as ".pad", + Available in macOS 12 or later (Mac Catalyst 15.0 or later). + Use this for controls that need to look the same between iOS and macOS. + */ + if traitCollection.userInterfaceIdiom == .mac { + button.preferredBehavioralStyle = .pad + } + + button.setTitle(NSLocalizedString("Button", comment: ""), for: .normal) + button.setTitle(NSLocalizedString("Pressed", comment: ""), for: .highlighted) + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + @available(iOS 15.0, *) + func configureToggleButton(button: UIButton) { + button.changesSelectionAsPrimaryAction = true // This makes the button style a "toggle button". + } + + func configureTitleTextButton(_ button: UIButton) { + // Note: Only for iOS the title's color can be changed. + button.setTitleColor(UIColor.systemGreen, for: [.normal]) + button.setTitleColor(UIColor.systemRed, for: [.highlighted]) + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + func configureBackgroundButton(_ button: UIButton) { + if #available(iOS 15, *) { + /** To keep the look the same betwen iOS and macOS: + For setBackgroundImage to work in Mac Catalyst, use UIBehavioralStyle as ".pad", + Available in macOS 12 or later (Mac Catalyst 15.0 or later). + Use this for controls that need to look the same between iOS and macOS. + */ + if traitCollection.userInterfaceIdiom == .mac { + button.preferredBehavioralStyle = .pad + } + } + + button.setBackgroundImage(UIImage(named: "background"), for: .normal) + button.setBackgroundImage(UIImage(named: "background_highlighted"), for: .highlighted) + button.setBackgroundImage(UIImage(named: "background_disabled"), for: .disabled) + + button.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) + } + + // This handler is called when this button needs updating. + @available(iOS 15.0, *) + func configureUpdateActivityHandlerButton(_ button: UIButton) { + let activityUpdateHandler: (UIButton) -> Void = { button in + /// Shows an activity indicator in place of an image. Its placement is controlled by the `imagePlacement` property. + + // Start with the current button's configuration. + var config = button.configuration + config?.showsActivityIndicator = button.isSelected ? false : true + button.configuration = config + } + + var buttonConfig = UIButton.Configuration.plain() + buttonConfig.image = UIImage(systemName: "tray") + buttonConfig.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(textStyle: .body) + button.configuration = buttonConfig + + // Set the button's title and font. + button.setTitle(NSLocalizedString("Button", comment: ""), for: []) + button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body) + + button.changesSelectionAsPrimaryAction = true // This turns on the toggle behavior. + button.configurationUpdateHandler = activityUpdateHandler + + // For this button to include an activity indicator next to the title, keep the iPad behavior. + if traitCollection.userInterfaceIdiom == .mac { + button.preferredBehavioralStyle = .pad + } + + button.addTarget(self, action: #selector(ButtonViewController.toggleButtonClicked(_:)), for: .touchUpInside) + } + + @available(iOS 15.0, *) + func configureUpdateHandlerButton(_ button: UIButton) { + // This is called when a button needs an update. + let colorUpdateHandler: (UIButton) -> Void = { button in + button.configuration?.baseBackgroundColor = button.isSelected + ? UIColor.systemPink.withAlphaComponent(0.4) + : UIColor.systemPink + } + + let buttonConfig = UIButton.Configuration.filled() + button.configuration = buttonConfig + + button.changesSelectionAsPrimaryAction = true // This turns on the toggle behavior. + button.configurationUpdateHandler = colorUpdateHandler + + // For this button to use baseBackgroundColor for the visual toggle state, keep the iPad behavior. + if traitCollection.userInterfaceIdiom == .mac { + button.preferredBehavioralStyle = .pad + } + + button.addTarget(self, action: #selector(ButtonViewController.toggleButtonClicked(_:)), for: .touchUpInside) + } + + @available(iOS 15.0, *) + func configureUpdateImageHandlerButton(_ button: UIButton) { + // This is called when a button needs an update. + let colorUpdateHandler: (UIButton) -> Void = { button in + button.configuration?.image = + button.isSelected ? UIImage(systemName: "cart.fill") : UIImage(systemName: "cart") + button.toolTip = + button.isSelected ? + NSLocalizedString("CartFilledButtonToolTipTitle", comment: "") : + NSLocalizedString("CartEmptyButtonToolTipTitle", comment: "") + } + + var buttonConfig = UIButton.Configuration.plain() + buttonConfig.image = UIImage(systemName: "cart") + buttonConfig.preferredSymbolConfigurationForImage = UIImage.SymbolConfiguration(textStyle: .largeTitle) + button.configuration = buttonConfig + + button.changesSelectionAsPrimaryAction = true // This turns on the toggle behavior. + button.configurationUpdateHandler = colorUpdateHandler + + // For this button to use the updateHandler to change it's icon for the visual toggle state, keep the iPad behavior. + if traitCollection.userInterfaceIdiom == .mac { + button.preferredBehavioralStyle = .pad + } + + button.setTitle("", for: []) // No title, just an image. + button.isSelected = false + + button.addTarget(self, action: #selector(ButtonViewController.toggleButtonClicked(_:)), for: .touchUpInside) + } + + // MARK: - Add To Cart Button + + @available(iOS 15.0, *) + func toolTipInteraction(_ interaction: UIToolTipInteraction, configurationAt point: CGPoint) -> UIToolTipConfiguration? { + let formatString = NSLocalizedString("Cart Tooltip String", + comment: "Cart Tooltip String format to be found in Localizable.stringsdict") + let resultString = String.localizedStringWithFormat(formatString, cartItemCount) + return UIToolTipConfiguration(toolTip: resultString) + } + + @available(iOS 15.0, *) + func addToCart(action: UIAction) { + cartItemCount = cartItemCount > 0 ? 0 : 12 + if let button = action.sender as? UIButton { + button.setNeedsUpdateConfiguration() + } + } + + @available(iOS 15.0, *) + func configureAddToCartButton(_ button: UIButton) { + var config = UIButton.Configuration.filled() + config.buttonSize = .large + config.image = UIImage(systemName: "cart.fill") + config.title = "Add to Cart" + config.cornerStyle = .capsule + config.baseBackgroundColor = UIColor.systemTeal + button.configuration = config + + button.toolTip = "" // The value will be determined in its delegate. + button.toolTipInteraction?.delegate = self + + button.addAction(UIAction(handler: addToCart(action:)), for: .touchUpInside) + + // For this button to include subtitle and larger size, the behavioral style needs to be set to ".pad". + if traitCollection.userInterfaceIdiom == .mac { + button.preferredBehavioralStyle = .pad + } + + button.changesSelectionAsPrimaryAction = true // This turns on the toggle behavior. + + // This handler is called when this button needs updating. + button.configurationUpdateHandler = { + [unowned self] button in + + // Start with the current button's configuration. + var newConfig = button.configuration + + if button.isSelected { + // The button was clicked or tapped. + newConfig?.image = cartItemCount > 0 + ? UIImage(systemName: "cart.fill.badge.plus") + : UIImage(systemName: "cart.badge.plus") + + let formatString = NSLocalizedString("Cart Items String", + comment: "Cart Items String format to be found in Localizable.stringsdict") + let resultString = String.localizedStringWithFormat(formatString, cartItemCount) + newConfig?.subtitle = resultString + } else { + // As the button is highlighted (pressed), apply a temporary image and subtitle. + newConfig?.image = UIImage(systemName: "cart.fill") + newConfig?.subtitle = "" + } + + newConfig?.imagePadding = 8 // Add a litle more space between the icon and button title. + + // Note: To change the padding between the title and subtitle, set "titlePadding". + // Note: To change the padding around the perimeter of the button, set "contentInsets". + + button.configuration = newConfig + } + } + + // MARK: - Button Actions + + @objc + func buttonClicked(_ sender: UIButton) { + Swift.debugPrint("Button was clicked.") + } + + @objc + func toggleButtonClicked(_ sender: UIButton) { + Swift.debugPrint("Toggle action: \(sender)") + } + +} diff --git a/UIKitCatalog/ButtonViewController.swift b/UIKitCatalog/ButtonViewController.swift index 258b4a7..6ce42f7 100755 --- a/UIKitCatalog/ButtonViewController.swift +++ b/UIKitCatalog/ButtonViewController.swift @@ -11,138 +11,146 @@ A view controller that demonstrates how to use `UIButton`. import UIKit -class ButtonViewController: UITableViewController { +class ButtonViewController: BaseTableViewController { + + // Cell identifier for each button table view cell. + enum ButtonKind: String, CaseIterable { + case buttonSystem + case buttonDetailDisclosure + case buttonSystemAddContact + case buttonClose + case buttonStyleGray + case buttonStyleTinted + case buttonStyleFilled + case buttonCornerStyle + case buttonToggle + case buttonTitleColor + case buttonImage + case buttonAttrText + case buttonSymbol + case buttonLargeSymbol + case buttonTextSymbol + case buttonSymbolText + case buttonMultiTitle + case buttonBackground + case addToCartButton + case buttonUpdateActivityHandler + case buttonUpdateHandler + case buttonImageUpdateHandler + } + // MARK: - Properties - @IBOutlet weak var systemTextButton: UIButton! - @IBOutlet weak var systemContactAddButton: UIButton! - @IBOutlet weak var systemDetailDisclosureButton: UIButton! - @IBOutlet weak var imageButton: UIButton! - @IBOutlet weak var attributedTextButton: UIButton! - @IBOutlet weak var symbolButton: UIButton! - @IBOutlet weak var symbolTextButton: UIButton! - @IBOutlet weak var menuButton: UIButton! + // "Add to Cart" Button + var cartItemCount: Int = 0 - // MARK: - View Life Cycle - override func viewDidLoad() { super.viewDidLoad() - - // All of the buttons are created in the storyboard, but configured below. - configureSystemTextButton() - configureSystemContactAddButton() - configureSystemDetailDisclosureButton() - configureImageButton() - configureAttributedTextSystemButton() - configureSymbolButton() - configureSymbolTextButton() - configureMenuButton() - } - - // MARK: - Configuration - - func configureSystemTextButton() { - let buttonTitle = NSLocalizedString("Button", comment: "") - - systemTextButton.setTitle(buttonTitle, for: .normal) - - systemTextButton.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) - } - - func configureSystemContactAddButton() { - systemContactAddButton.backgroundColor = UIColor.clear - - systemContactAddButton.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) - } - - func configureSystemDetailDisclosureButton() { - systemDetailDisclosureButton.backgroundColor = UIColor.clear - - systemDetailDisclosureButton.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) - } - - func configureImageButton() { - // To create this button in code you can use `UIButton.init(type: .system)`. - - // Set the tint color to the button's image. - if let image = UIImage(named: "x_icon") { - let imageButtonNormalImage = image.withTintColor(.systemPurple) - imageButton.setImage(imageButtonNormalImage, for: .normal) + + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("DefaultTitle", comment: ""), + cellID: ButtonKind.buttonSystem.rawValue, + configHandler: configureSystemTextButton), + CaseElement(title: NSLocalizedString("DetailDisclosureTitle", comment: ""), + cellID: ButtonKind.buttonDetailDisclosure.rawValue, + configHandler: configureSystemDetailDisclosureButton), + CaseElement(title: NSLocalizedString("AddContactTitle", comment: ""), + cellID: ButtonKind.buttonSystemAddContact.rawValue, + configHandler: configureSystemContactAddButton), + CaseElement(title: NSLocalizedString("CloseTitle", comment: ""), + cellID: ButtonKind.buttonClose.rawValue, + configHandler: configureCloseButton) + ]) + + if #available(iOS 15, *) { + // These button styles are available on iOS 15 or later. + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("GrayTitle", comment: ""), + cellID: ButtonKind.buttonStyleGray.rawValue, + configHandler: configureStyleGrayButton), + CaseElement(title: NSLocalizedString("TintedTitle", comment: ""), + cellID: ButtonKind.buttonStyleTinted.rawValue, + configHandler: configureStyleTintedButton), + CaseElement(title: NSLocalizedString("FilledTitle", comment: ""), + cellID: ButtonKind.buttonStyleFilled.rawValue, + configHandler: configureStyleFilledButton), + CaseElement(title: NSLocalizedString("CornerStyleTitle", comment: ""), + cellID: ButtonKind.buttonCornerStyle.rawValue, + configHandler: configureCornerStyleButton), + CaseElement(title: NSLocalizedString("ToggleTitle", comment: ""), + cellID: ButtonKind.buttonToggle.rawValue, + configHandler: configureToggleButton) + ]) } + + if traitCollection.userInterfaceIdiom != .mac { + // Colored button titles only on iOS. + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("ButtonColorTitle", comment: ""), + cellID: ButtonKind.buttonTitleColor.rawValue, + configHandler: configureTitleTextButton) + ]) + } + + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("ImageTitle", comment: ""), + cellID: ButtonKind.buttonImage.rawValue, + configHandler: configureImageButton), + CaseElement(title: NSLocalizedString("AttributedStringTitle", comment: ""), + cellID: ButtonKind.buttonAttrText.rawValue, + configHandler: configureAttributedTextSystemButton), + CaseElement(title: NSLocalizedString("SymbolTitle", comment: ""), + cellID: ButtonKind.buttonSymbol.rawValue, + configHandler: configureSymbolButton) + ]) + + if #available(iOS 15, *) { + // This case uses UIButtonConfiguration which is available on iOS 15 or later. + if traitCollection.userInterfaceIdiom != .mac { + // UIButtonConfiguration for large images available only on iOS. + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("LargeSymbolTitle", comment: ""), + cellID: ButtonKind.buttonLargeSymbol.rawValue, + configHandler: configureLargeSymbolButton) + ]) + } + } + + if #available(iOS 15, *) { + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("StringSymbolTitle", comment: ""), + cellID: ButtonKind.buttonTextSymbol.rawValue, + configHandler: configureTextSymbolButton), + CaseElement(title: NSLocalizedString("SymbolStringTitle", comment: ""), + cellID: ButtonKind.buttonSymbolText.rawValue, + configHandler: configureSymbolTextButton), - // Add an accessibility label to the image. - imageButton.accessibilityLabel = NSLocalizedString("X", comment: "") - - imageButton.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) - } - - func configureAttributedTextSystemButton() { - let buttonTitle = NSLocalizedString("Button", comment: "") - - // Set the button's title for normal state. - let normalTitleAttributes: [NSAttributedString.Key: Any] = [ - NSAttributedString.Key.strikethroughStyle: NSUnderlineStyle.single.rawValue - ] - - let normalAttributedTitle = NSAttributedString(string: buttonTitle, attributes: normalTitleAttributes) - attributedTextButton.setAttributedTitle(normalAttributedTitle, for: .normal) - - // Set the button's title for highlighted state. - let highlightedTitleAttributes: [NSAttributedString.Key: Any] = [ - NSAttributedString.Key.foregroundColor: UIColor.systemGreen, - NSAttributedString.Key.strikethroughStyle: NSUnderlineStyle.thick.rawValue - ] - let highlightedAttributedTitle = NSAttributedString(string: buttonTitle, attributes: highlightedTitleAttributes) - attributedTextButton.setAttributedTitle(highlightedAttributedTitle, for: .highlighted) - - attributedTextButton.addTarget(self, action: #selector(ButtonViewController.buttonClicked(_:)), for: .touchUpInside) - } - - func configureSymbolButton() { - let buttonImage = UIImage(systemName: "person") - symbolButton.setImage(buttonImage, for: .normal) - - // Add an accessibility label to the image. - symbolButton.accessibilityLabel = NSLocalizedString("Person", comment: "") - - symbolButton.addTarget(self, - action: #selector(ButtonViewController.buttonClicked(_:)), - for: .touchUpInside) - } - - func configureSymbolTextButton() { - let buttonImage = UIImage(systemName: "person") - symbolTextButton.setImage(buttonImage, for: .normal) - - symbolTextButton.addTarget(self, - action: #selector(ButtonViewController.buttonClicked(_:)), - for: .touchUpInside) - - symbolTextButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body) - let config = UIImage.SymbolConfiguration(textStyle: .body, scale: .small) - symbolTextButton.setPreferredSymbolConfiguration(config, forImageIn: .normal) - } - - func menuHandler(action: UIAction) { - Swift.debugPrint("Menu Action '\(action.title)'") - } - - func configureMenuButton() { - let buttonTitle = NSLocalizedString("Button", comment: "") - menuButton.setTitle(buttonTitle, for: .normal) - - let items = (1...5).map { - UIAction(title: String(format: NSLocalizedString("ItemTitle", comment: ""), $0.description), handler: menuHandler) + CaseElement(title: NSLocalizedString("BackgroundTitle", comment: ""), + cellID: ButtonKind.buttonBackground.rawValue, + configHandler: configureBackgroundButton), + + // Multi-title button: title for normal and highlight state, setTitle(.highlighted) is for iOS 15 and later. + CaseElement(title: NSLocalizedString("MultiTitleTitle", comment: ""), + cellID: ButtonKind.buttonMultiTitle.rawValue, + configHandler: configureMultiTitleButton), + + // Various button effects done to the addToCartButton are available only on iOS 15 or later. + CaseElement(title: NSLocalizedString("AddToCartTitle", comment: ""), + cellID: ButtonKind.addToCartButton.rawValue, + configHandler: configureAddToCartButton), + + // UIButtonConfiguration with updateHandlers is available only on iOS 15 or later. + CaseElement(title: NSLocalizedString("UpdateActivityHandlerTitle", comment: ""), + cellID: ButtonKind.buttonUpdateActivityHandler.rawValue, + configHandler: configureUpdateActivityHandlerButton), + CaseElement(title: NSLocalizedString("UpdateHandlerTitle", comment: ""), + cellID: ButtonKind.buttonUpdateHandler.rawValue, + configHandler: configureUpdateHandlerButton), + CaseElement(title: NSLocalizedString("UpdateImageHandlerTitle", comment: ""), + cellID: ButtonKind.buttonImageUpdateHandler.rawValue, + configHandler: configureUpdateImageHandlerButton) + ]) } - menuButton.menu = UIMenu(title: NSLocalizedString("ChooseItemTitle", comment: ""), children: items) - menuButton.showsMenuAsPrimaryAction = true } - - // MARK: - Actions - @objc - func buttonClicked(_ sender: UIButton) { - print("A button was clicked: \(sender).") - } } - diff --git a/UIKitCatalog/CaseElement.swift b/UIKitCatalog/CaseElement.swift new file mode 100644 index 0000000..54f0ebd --- /dev/null +++ b/UIKitCatalog/CaseElement.swift @@ -0,0 +1,29 @@ +/* +See LICENSE folder for this sample’s licensing information. + +Abstract: +Test case element that serves our UITableViewCells. +*/ + +import UIKit + +struct CaseElement { + var title: String // Visual title of the cell (table section header title) + var cellID: String // Table view cell's identifier for searching for the cell within the nib file. + + typealias ConfigurationClosure = (UIView) -> Void + var configHandler: ConfigurationClosure // Configuration handler for setting up the cell's subview. + + init(title: String, cellID: String, configHandler: @escaping (V) -> Void) { + self.title = title + self.cellID = cellID + self.configHandler = { view in + guard let view = view as? V else { fatalError("Impossible") } + configHandler(view) + } + } + + func targetView(_ cell: UITableViewCell?) -> UIView? { + return cell != nil ? cell!.contentView.subviews[0] : nil + } +} diff --git a/UIKitCatalog/ColorPickerViewController.swift b/UIKitCatalog/ColorPickerViewController.swift index 04cb852..7783831 100755 --- a/UIKitCatalog/ColorPickerViewController.swift +++ b/UIKitCatalog/ColorPickerViewController.swift @@ -28,13 +28,14 @@ class ColorPickerViewController: UIViewController, UIColorPickerViewControllerDe configureColorWell() // For iOS, the picker button in the main view is not used, the color picker is presented from the navigation bar. - if navigationController!.traitCollection.userInterfaceIdiom != .mac { + if navigationController?.traitCollection.userInterfaceIdiom != .mac { pickerButton.isHidden = true } } // MARK: - UIColorWell + // Update the color view from the color well chosen action. func colorWellHandler(action: UIAction) { if let colorWell = action.sender as? UIColorWell { colorView.backgroundColor = colorWell.selectedColor @@ -48,11 +49,10 @@ class ColorPickerViewController: UIViewController, UIColorPickerViewControllerDe */ let colorWellAction = UIAction(title: "", handler: colorWellHandler) colorWell = - UIColorWell(frame: CGRect(x: 0, y: 0, width: 32, height: 32), - primaryAction: colorWellAction) + UIColorWell(frame: CGRect(x: 0, y: 0, width: 32, height: 32), primaryAction: colorWellAction) // For Mac Catalyst, the UIColorWell is placed in the main view. - if navigationController!.traitCollection.userInterfaceIdiom == .mac { + if navigationController?.traitCollection.userInterfaceIdiom == .mac { pickerWellView.addSubview(colorWell) } else { // For iOS, the UIColorWell is placed inside the navigation bar as a UIBarButtonItem. @@ -75,7 +75,7 @@ class ColorPickerViewController: UIViewController, UIColorPickerViewControllerDe // Present the color picker from the UIBarButtonItem, iOS only. // This will present it as a popover (preferred), or for compact mode as a modal sheet. @IBAction func presentColorPickerByBarButton(_ sender: UIBarButtonItem) { - colorPicker.modalPresentationStyle = UIModalPresentationStyle.popover + colorPicker.modalPresentationStyle = UIModalPresentationStyle.popover // will display as popover for iPad or sheet for compact screens. let popover: UIPopoverPresentationController = colorPicker.popoverPresentationController! popover.barButtonItem = sender present(colorPicker, animated: true, completion: nil) @@ -85,19 +85,53 @@ class ColorPickerViewController: UIViewController, UIColorPickerViewControllerDe // This will present it as a popover (preferred), or for compact mode as a modal sheet. @IBAction func presentColorPickerByButton(_ sender: UIButton) { colorPicker.modalPresentationStyle = UIModalPresentationStyle.popover - let popover: UIPopoverPresentationController = colorPicker.popoverPresentationController! - popover.sourceView = sender - present(colorPicker, animated: true, completion: nil) + if let popover = colorPicker.popoverPresentationController { + popover.sourceView = sender + present(colorPicker, animated: true, completion: nil) + } } // MARK: - UIColorPickerViewControllerDelegate + // Color returned from the color picker via UIBarButtonItem - iOS 15.0 + @available(iOS 15.0, *) + func colorPickerViewController(_ viewController: UIColorPickerViewController, didSelect color: UIColor, continuously: Bool) { + // User has chosen a color. + let chosenColor = viewController.selectedColor + colorView.backgroundColor = chosenColor + + // Dismiss the color picker if the conditions are right: + // 1) User is not doing a continous pick (tap and drag across multiple colors). + // 2) Picker is presented on a non-compact device. + // + // Use the following check to determine how the color picker was presented (modal or popover). + // For popover, we want to dismiss it when a color is locked. + // For modal, the picker has a close button. + // + if !continuously { + if traitCollection.horizontalSizeClass != .compact { + viewController.dismiss(animated: true, completion: { + Swift.debugPrint("\(chosenColor)") + }) + } + } + } + + // Color returned from the color picker - iOS 14.x and earlier. func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) { // User has chosen a color. let chosenColor = viewController.selectedColor colorView.backgroundColor = chosenColor - Swift.debugPrint("\(chosenColor)") + // Use the following check to determine how the color picker was presented (modal or popover). + // For popover, we want to dismiss it when a color is locked. + // For modal, the picker has a close button. + // + if traitCollection.horizontalSizeClass != .compact { + viewController.dismiss(animated: true, completion: { + Swift.debugPrint("\(chosenColor)") + }) + } } func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) { @@ -108,16 +142,3 @@ class ColorPickerViewController: UIViewController, UIColorPickerViewControllerDe } } - -extension UIColor { - var colorComponents: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)? { - guard let components = self.cgColor.components else { return nil } - - return ( - red: components[0], - green: components[1], - blue: components[2], - alpha: components[3] - ) - } -} diff --git a/UIKitCatalog/CustomPageControlViewController.swift b/UIKitCatalog/CustomPageControlViewController.swift index 7f2d316..8111b05 100755 --- a/UIKitCatalog/CustomPageControlViewController.swift +++ b/UIKitCatalog/CustomPageControlViewController.swift @@ -85,7 +85,7 @@ class CustomPageControlViewController: UIViewController { @objc func pageControlValueDidChange() { // Note: gesture swiping between pages is provided by `UIPageViewController` and not `UIPageControl`. - print("The page control changed its current page to \(pageControl.currentPage).") + Swift.debugPrint("The page control changed its current page to \(pageControl.currentPage).") colorView.backgroundColor = colors[pageControl.currentPage] } diff --git a/UIKitCatalog/CustomSearchBarViewController.swift b/UIKitCatalog/CustomSearchBarViewController.swift index af4bf8b..b31551f 100755 --- a/UIKitCatalog/CustomSearchBarViewController.swift +++ b/UIKitCatalog/CustomSearchBarViewController.swift @@ -43,19 +43,19 @@ class CustomSearchBarViewController: UIViewController { extension CustomSearchBarViewController: UISearchBarDelegate { func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { - print("The custom search bar keyboard \"Search\" button was tapped.") + Swift.debugPrint("The custom search bar keyboard \"Search\" button was tapped.") searchBar.resignFirstResponder() } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { - print("The custom search bar \"Cancel\" button was tapped.") + Swift.debugPrint("The custom search bar \"Cancel\" button was tapped.") searchBar.resignFirstResponder() } func searchBarBookmarkButtonClicked(_ searchBar: UISearchBar) { - print("The custom \"bookmark button\" inside the search bar was tapped.") + Swift.debugPrint("The custom \"bookmark button\" inside the search bar was tapped.") } } diff --git a/UIKitCatalog/CustomToolbarViewController.swift b/UIKitCatalog/CustomToolbarViewController.swift index 2574b7c..a381332 100755 --- a/UIKitCatalog/CustomToolbarViewController.swift +++ b/UIKitCatalog/CustomToolbarViewController.swift @@ -57,7 +57,7 @@ class CustomToolbarViewController: UIViewController { let attributes = [ NSAttributedString.Key.foregroundColor: UIColor.systemPurple ] - barButtonItem.setTitleTextAttributes(attributes, for: .normal) + barButtonItem.setTitleTextAttributes(attributes, for: []) return barButtonItem } @@ -66,7 +66,7 @@ class CustomToolbarViewController: UIViewController { @objc func barButtonItemClicked(_ barButtonItem: UIBarButtonItem) { - print("A bar button item on the custom toolbar was clicked: \(barButtonItem).") + Swift.debugPrint("A bar button item on the custom toolbar was clicked: \(barButtonItem).") } } diff --git a/UIKitCatalog/DatePickerController.swift b/UIKitCatalog/DatePickerController.swift index 3a39a7f..464e479 100755 --- a/UIKitCatalog/DatePickerController.swift +++ b/UIKitCatalog/DatePickerController.swift @@ -29,6 +29,12 @@ class DatePickerController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + if #available(iOS 15, *) { + // In case the label's content is too large to fit inside the label (causing truncation), + // use this to reveal the label's full text drawn as a tool tip. + dateLabel.showsExpansionTextWhenTruncated = true + } + configureDatePicker() } diff --git a/UIKitCatalog/DefaultPageControlViewController.swift b/UIKitCatalog/DefaultPageControlViewController.swift index 65c5ca6..42f66cf 100755 --- a/UIKitCatalog/DefaultPageControlViewController.swift +++ b/UIKitCatalog/DefaultPageControlViewController.swift @@ -55,7 +55,7 @@ class PageControlViewController: UIViewController { @objc func pageControlValueDidChange() { // Note: gesture swiping between pages is provided by `UIPageViewController` and not `UIPageControl`. - print("The page control changed its current page to \(pageControl.currentPage).") + Swift.debugPrint("The page control changed its current page to \(pageControl.currentPage).") colorView.backgroundColor = colors[pageControl.currentPage] } diff --git a/UIKitCatalog/DefaultSearchBarViewController.swift b/UIKitCatalog/DefaultSearchBarViewController.swift index 51c08df..20199bf 100755 --- a/UIKitCatalog/DefaultSearchBarViewController.swift +++ b/UIKitCatalog/DefaultSearchBarViewController.swift @@ -38,17 +38,17 @@ class DefaultSearchBarViewController: UIViewController { extension DefaultSearchBarViewController: UISearchBarDelegate { func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) { - print("The default search selected scope button index changed to \(selectedScope).") + Swift.debugPrint("The default search selected scope button index changed to \(selectedScope).") } func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { - print("The default search bar keyboard search button was tapped: \(String(describing: searchBar.text)).") + Swift.debugPrint("The default search bar keyboard search button was tapped: \(String(describing: searchBar.text)).") searchBar.resignFirstResponder() } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { - print("The default search bar cancel button was tapped.") + Swift.debugPrint("The default search bar cancel button was tapped.") searchBar.resignFirstResponder() } diff --git a/UIKitCatalog/DefaultToolbarViewController.swift b/UIKitCatalog/DefaultToolbarViewController.swift index bd7839a..5b8717f 100755 --- a/UIKitCatalog/DefaultToolbarViewController.swift +++ b/UIKitCatalog/DefaultToolbarViewController.swift @@ -55,6 +55,6 @@ class DefaultToolbarViewController: UIViewController { @objc func barButtonItemClicked(_ barButtonItem: UIBarButtonItem) { - print("A bar button item on the default toolbar was clicked: \(barButtonItem).") + Swift.debugPrint("A bar button item on the default toolbar was clicked: \(barButtonItem).") } } diff --git a/UIKitCatalog/FontPickerViewController.swift b/UIKitCatalog/FontPickerViewController.swift index 6e28e7d..5d88e02 100755 --- a/UIKitCatalog/FontPickerViewController.swift +++ b/UIKitCatalog/FontPickerViewController.swift @@ -26,10 +26,10 @@ class FontPickerViewController: UIViewController { configureFontPicker() - #if !targetEnvironment(macCatalyst) + if traitCollection.userInterfaceIdiom != .mac { // UITextFormattingCoordinator's toggleFontPanel is available only for macOS. textFormatterButton.isHidden = true - #endif + } } override func viewDidAppear(_ animated: Bool) { diff --git a/UIKitCatalog/ImageViewController.swift b/UIKitCatalog/ImageViewController.swift index 70401e8..11953a2 100755 --- a/UIKitCatalog/ImageViewController.swift +++ b/UIKitCatalog/ImageViewController.swift @@ -32,6 +32,13 @@ class ImageViewController: UIViewController { imageView.isAccessibilityElement = true imageView.accessibilityLabel = NSLocalizedString("Animated", comment: "") + + if #available(iOS 15, *) { + // This case uses UIToolTipInteraction which is available on iOS 15 or later. + let interaction = + UIToolTipInteraction(defaultToolTip: NSLocalizedString("ImageToolTipTitle", comment: "")) + imageView.addInteraction(interaction) + } } } } diff --git a/UIKitCatalog/MenuButtonViewController.swift b/UIKitCatalog/MenuButtonViewController.swift new file mode 100755 index 0000000..15f2a92 --- /dev/null +++ b/UIKitCatalog/MenuButtonViewController.swift @@ -0,0 +1,184 @@ +/* +See LICENSE folder for this sample’s licensing information. + +Abstract: +A view controller that demonstrates how to attach menus to `UIButton`. +*/ + +import UIKit + +class MenuButtonViewController: BaseTableViewController { + + // Cell identifier for each menu button table view cell. + enum MenuButtonKind: String, CaseIterable { + case buttonMenuProgrammatic + case buttonMenuMultiAction + case buttonSubMenu + case buttonMenuSelection + } + + override func viewDidLoad() { + super.viewDidLoad() + + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("DropDownProgTitle", comment: ""), + cellID: MenuButtonKind.buttonMenuProgrammatic.rawValue, + configHandler: configureDropDownProgrammaticButton), + CaseElement(title: NSLocalizedString("DropDownMultiActionTitle", comment: ""), + cellID: MenuButtonKind.buttonMenuMultiAction.rawValue, + configHandler: configureDropdownMultiActionButton), + CaseElement(title: NSLocalizedString("DropDownButtonSubMenuTitle", comment: ""), + cellID: MenuButtonKind.buttonSubMenu.rawValue, + configHandler: configureDropdownSubMenuButton), + CaseElement(title: NSLocalizedString("PopupSelection", comment: ""), + cellID: MenuButtonKind.buttonMenuSelection.rawValue, + configHandler: configureSelectionPopupButton) + ]) + } + + // MARK: - Handlers + + enum ButtonMenuActionIdentifiers: String { + case item1 + case item2 + case item3 + } + func menuHandler(action: UIAction) { + switch action.identifier.rawValue { + case ButtonMenuActionIdentifiers.item1.rawValue: + Swift.debugPrint("Menu Action: item 1") + case ButtonMenuActionIdentifiers.item2.rawValue: + Swift.debugPrint("Menu Action: item 2") + case ButtonMenuActionIdentifiers.item3.rawValue: + Swift.debugPrint("Menu Action: item 3") + default: break + } + } + + func item4Handler(action: UIAction) { + Swift.debugPrint("Menu Action: \(action.title)") + } + + // MARK: - Drop Down Menu Buttons + + func configureDropDownProgrammaticButton(button: UIButton) { + button.menu = UIMenu(children: [ + UIAction(title: String(format: NSLocalizedString("ItemTitle", comment: ""), "1"), + identifier: UIAction.Identifier(ButtonMenuActionIdentifiers.item1.rawValue), + handler: menuHandler), + UIAction(title: String(format: NSLocalizedString("ItemTitle", comment: ""), "2"), + identifier: UIAction.Identifier(ButtonMenuActionIdentifiers.item2.rawValue), + handler: menuHandler) + ]) + + button.showsMenuAsPrimaryAction = true + } + + func configureDropdownMultiActionButton(button: UIButton) { + let buttonMenu = UIMenu(children: [ + // Share a single handler for the first 3 actions. + UIAction(title: String(format: NSLocalizedString("ItemTitle", comment: ""), "1"), + image: UIImage(systemName: "1.circle"), + identifier: UIAction.Identifier(ButtonMenuActionIdentifiers.item1.rawValue), + attributes: [], + handler: menuHandler), + UIAction(title: String(format: NSLocalizedString("ItemTitle", comment: ""), "2"), + image: UIImage(systemName: "2.circle"), + identifier: UIAction.Identifier(ButtonMenuActionIdentifiers.item2.rawValue), + handler: menuHandler), + UIAction(title: String(format: NSLocalizedString("ItemTitle", comment: ""), "3"), + image: UIImage(systemName: "3.circle"), + identifier: UIAction.Identifier(ButtonMenuActionIdentifiers.item3.rawValue), + handler: menuHandler), + + // Use a separate handler for this 4th action. + UIAction(title: String(format: NSLocalizedString("ItemTitle", comment: ""), "4"), + image: UIImage(systemName: "4.circle"), + identifier: nil, + handler: item4Handler(action:)), + + // Use a closure for the 5th action. + UIAction(title: String(format: NSLocalizedString("ItemTitle", comment: ""), "5"), + image: UIImage(systemName: "5.circle"), + identifier: nil) { action in + Swift.debugPrint("Menu Action: \(action.title)") + }, + + // Use attributes to make the 6th action disabled. + UIAction(title: String(format: NSLocalizedString("ItemTitle", comment: ""), "6"), + image: UIImage(systemName: "6.circle"), + identifier: nil, + attributes: [UIMenuElement.Attributes.disabled]) { action in + Swift.debugPrint("Menu Action: \(action.title)") + } + ]) + button.menu = buttonMenu + + // This makes the button behave like a drop down menu. + button.showsMenuAsPrimaryAction = true + } + + func configureDropdownSubMenuButton(button: UIButton) { + let sortClosure = { (action: UIAction) in + Swift.debugPrint("Sort by: \(action.title)") + } + let refreshClosure = { (action: UIAction) in + Swift.debugPrint("Refresh handler") + } + let accountHandler = { (action: UIAction) in + Swift.debugPrint("Account handler") + } + + var sortMenu: UIMenu + if #available(iOS 15, *) { // .singleSelection option only on iOS 15 or later + // The sort sub menu supports a selection. + sortMenu = UIMenu(title: "Sort By", options: .singleSelection, children: [ + UIAction(title: "Date", state: .on, handler: sortClosure), + UIAction(title: "Size", handler: sortClosure) + ]) + } else { + sortMenu = UIMenu(title: "Sort By", children: [ + UIAction(title: "Date", handler: sortClosure), + UIAction(title: "Size", handler: sortClosure) + ]) + } + + let topMenu = UIMenu(children: [ + UIAction(title: "Refresh", handler: refreshClosure), + UIAction(title: "Account", handler: accountHandler), + sortMenu + ]) + + // This makes the button behave like a drop down menu. + button.showsMenuAsPrimaryAction = true + button.menu = topMenu + } + + // MARK: - Selection Popup Menu Button + + func updateColor(_ title: String) { + Swift.debugPrint("Color selected: \(title)") + } + + func configureSelectionPopupButton(button: UIButton) { + let colorClosure = { [unowned self] (action: UIAction) in + self.updateColor(action.title) + } + + button.menu = UIMenu(children: [ + UIAction(title: "Red", handler: colorClosure), + UIAction(title: "Green", state: .on, handler: colorClosure), // The default selected item (green). + UIAction(title: "Blue", handler: colorClosure) + ]) + + // This makes the button behave like a drop down menu. + button.showsMenuAsPrimaryAction = true + + if #available(iOS 15, *) { + button.changesSelectionAsPrimaryAction = true + // Select the default menu item (green). + updateColor((button.menu?.selectedElements.first!.title)!) + } + } + +} diff --git a/UIKitCatalog/OutlineViewController.swift b/UIKitCatalog/OutlineViewController.swift index 7036974..499f494 100755 --- a/UIKitCatalog/OutlineViewController.swift +++ b/UIKitCatalog/OutlineViewController.swift @@ -39,6 +39,8 @@ class OutlineViewController: UIViewController { var dataSource: UICollectionViewDiffableDataSource! = nil var outlineCollectionView: UICollectionView! = nil + private var detailTargetChangeObserver: Any? = nil + override func viewDidLoad() { super.viewDidLoad() @@ -48,52 +50,68 @@ class OutlineViewController: UIViewController { // Add a translucent background to the primary view controller for the Mac. splitViewController!.primaryBackgroundStyle = .sidebar view.backgroundColor = UIColor.clear - - // Listen for when a split view controller is expanded or collapsed. - NotificationCenter.default.addObserver( - self, - selector: #selector(showDetailTargetDidChange(_:)), - name: UIViewController.showDetailTargetDidChangeNotification, - object: nil) + + // Listen for when the split view controller is expanded or collapsed for iPad multi-tasking, + // and on device rotate (iPhones that support regular size class). + detailTargetChangeObserver = + NotificationCenter.default.addObserver(forName: UIViewController.showDetailTargetDidChangeNotification, + object: nil, + queue: OperationQueue.main, + using: { _ in + // Posted when a split view controller is expanded or collapsed. + + // Re-load the data source, the disclosure indicators need to change (push vs. present on a cell). + var snapshot = self.dataSource.snapshot() + snapshot.reloadItems(self.menuItems) + self.dataSource.apply(snapshot, animatingDifferences: false) + }) if navigationController!.traitCollection.userInterfaceIdiom == .mac { navigationController!.navigationBar.isHidden = true } } - // Posted when a split view controller is expanded or collapsed. - @objc - func showDetailTargetDidChange(_ notification: NSNotification) { - // Reaload the data source, the disclosure indicators need to change (push vs. present on a cell). - var snapshot = dataSource.snapshot() - snapshot.reloadItems(menuItems) - dataSource.apply(snapshot, animatingDifferences: false) - } - deinit { - NotificationCenter.default.removeObserver(self, name: UIViewController.showDetailTargetDidChangeNotification, object: nil) + if let observer = detailTargetChangeObserver { + NotificationCenter.default.removeObserver(observer) + } } lazy var controlsOutlineItem: OutlineItem = { - var controlsSubItems = [ - OutlineItem(title: NSLocalizedString("ButtonsTitle", comment: ""), imageName: nil, + + // Determine the content of the UIButton grouping. + var buttonItems = [ + OutlineItem(title: NSLocalizedString("ButtonsTitle", comment: ""), imageName: "rectangle", storyboardName: "ButtonViewController"), + OutlineItem(title: NSLocalizedString("MenuButtonsTitle", comment: ""), imageName: "list.bullet.rectangle", + storyboardName: "MenuButtonViewController") + ] + // UIPointerInteraction to UIButtons is applied for iPad. + if navigationController!.traitCollection.userInterfaceIdiom == .pad { + buttonItems.append(contentsOf: + [OutlineItem(title: NSLocalizedString("PointerInteractionButtonsTitle", comment: ""), + imageName: "cursorarrow.rays", + storyboardName: "PointerInteractionButtonViewController") ]) + } + + var controlsSubItems = [ + OutlineItem(title: NSLocalizedString("ButtonsTitle", comment: ""), imageName: "rectangle.on.rectangle", subitems: buttonItems), - OutlineItem(title: NSLocalizedString("PageControlTitle", comment: ""), imageName: nil, subitems: [ + OutlineItem(title: NSLocalizedString("PageControlTitle", comment: ""), imageName: "photo.on.rectangle", subitems: [ OutlineItem(title: NSLocalizedString("DefaultPageControlTitle", comment: ""), imageName: nil, storyboardName: "DefaultPageControlViewController"), OutlineItem(title: NSLocalizedString("CustomPageControlTitle", comment: ""), imageName: nil, storyboardName: "CustomPageControlViewController") ]), - OutlineItem(title: NSLocalizedString("SearchBarsTitle", comment: ""), imageName: nil, subitems: [ + OutlineItem(title: NSLocalizedString("SearchBarsTitle", comment: ""), imageName: "magnifyingglass", subitems: [ OutlineItem(title: NSLocalizedString("DefaultSearchBarTitle", comment: ""), imageName: nil, storyboardName: "DefaultSearchBarViewController"), OutlineItem(title: NSLocalizedString("CustomSearchBarTitle", comment: ""), imageName: nil, storyboardName: "CustomSearchBarViewController") ]), - OutlineItem(title: NSLocalizedString("SegmentedControlsTitle", comment: ""), imageName: nil, + OutlineItem(title: NSLocalizedString("SegmentedControlsTitle", comment: ""), imageName: "square.split.3x1", storyboardName: "SegmentedControlViewController"), OutlineItem(title: NSLocalizedString("SlidersTitle", comment: ""), imageName: nil, storyboardName: "SliderViewController"), @@ -103,15 +121,12 @@ class OutlineViewController: UIViewController { storyboardName: "TextFieldViewController") ] - #if !targetEnvironment(macCatalyst) - /** Because this sample has "Optimize Interface for Mac" turned on - - UIStepper class is not supported when running Mac Catalyst apps in the Mac idiom. - */ - let stepperItem = - OutlineItem(title: NSLocalizedString("SteppersTitle", comment: ""), imageName: nil, - storyboardName: "StepperViewController") - controlsSubItems.append(stepperItem) - #endif + if traitCollection.userInterfaceIdiom != .mac { + // UIStepper class is not supported when running Mac Catalyst apps in the Mac idiom. + let stepperItem = + OutlineItem(title: NSLocalizedString("SteppersTitle", comment: ""), imageName: nil, storyboardName: "StepperViewController") + controlsSubItems.append(stepperItem) + } return OutlineItem(title: "Controls", imageName: "slider.horizontal.3", subitems: controlsSubItems) }() @@ -128,15 +143,13 @@ class OutlineViewController: UIViewController { storyboardName: "ImagePickerViewController") ] - #if !targetEnvironment(macCatalyst) - /** Because this sample has "Optimize Interface for Mac" turned on - - UIPickerView class is not supported when running Mac Catalyst apps in the Mac idiom. - */ - let pickerViewItem = - OutlineItem(title: NSLocalizedString("PickerViewTitle", comment: ""), imageName: nil, - storyboardName: "PickerViewController") - pickerSubItems.append(pickerViewItem) - #endif + if traitCollection.userInterfaceIdiom != .mac { + // UIPickerView class is not supported when running Mac Catalyst apps in the Mac idiom. + // To use a picker in macOS, use UIButton with changesSelectionAsPrimaryAction set to "true". + let pickerViewItem = + OutlineItem(title: NSLocalizedString("PickerViewTitle", comment: ""), imageName: nil, storyboardName: "PickerViewController") + pickerSubItems.append(pickerViewItem) + } return OutlineItem(title: "Pickers", imageName: "list.bullet", subitems: pickerSubItems) }() @@ -147,14 +160,22 @@ class OutlineViewController: UIViewController { storyboardName: "ActivityIndicatorViewController"), OutlineItem(title: NSLocalizedString("AlertControllersTitle", comment: ""), imageName: nil, storyboardName: "AlertControllerViewController"), - OutlineItem(title: NSLocalizedString("ImageViewTitle", comment: ""), imageName: nil, - storyboardName: "ImageViewController"), + OutlineItem(title: NSLocalizedString("TextViewTitle", comment: ""), imageName: nil, + storyboardName: "TextViewController"), + + OutlineItem(title: NSLocalizedString("ImagesTitle", comment: ""), imageName: "photo", subitems: [ + OutlineItem(title: NSLocalizedString("ImageViewTitle", comment: ""), imageName: nil, + storyboardName: "ImageViewController"), + OutlineItem(title: NSLocalizedString("SymbolsTitle", comment: ""), imageName: nil, + storyboardName: "SymbolViewController") + ]), + OutlineItem(title: NSLocalizedString("ProgressViewsTitle", comment: ""), imageName: nil, storyboardName: "ProgressViewController"), OutlineItem(title: NSLocalizedString("StackViewsTitle", comment: ""), imageName: nil, storyboardName: "StackViewController"), - OutlineItem(title: NSLocalizedString("ToolbarsTitle", comment: ""), imageName: nil, subitems: [ + OutlineItem(title: NSLocalizedString("ToolbarsTitle", comment: ""), imageName: "hammer", subitems: [ OutlineItem(title: NSLocalizedString("DefaultToolBarTitle", comment: ""), imageName: nil, storyboardName: "DefaultToolbarViewController"), OutlineItem(title: NSLocalizedString("TintedToolbarTitle", comment: ""), imageName: nil, @@ -163,8 +184,9 @@ class OutlineViewController: UIViewController { storyboardName: "CustomToolbarViewController") ]), - OutlineItem(title: NSLocalizedString("WebViewTitle", comment: ""), imageName: nil, - storyboardName: "WebViewController") + OutlineItem(title: NSLocalizedString("VisualEffectTitle", comment: ""), imageName: nil, storyboardName: "VisualEffectViewController"), + + OutlineItem(title: NSLocalizedString("WebViewTitle", comment: ""), imageName: nil, storyboardName: "WebViewController") ]) }() @@ -198,24 +220,29 @@ extension OutlineViewController { var contentConfiguration = cell.defaultContentConfiguration() contentConfiguration.text = menuItem.title - if menuItem.imageName != nil { - contentConfiguration.image = UIImage(systemName: menuItem.imageName!) + if let image = menuItem.imageName { + contentConfiguration.image = UIImage(systemName: image) } contentConfiguration.textProperties.font = .preferredFont(forTextStyle: .headline) cell.contentConfiguration = contentConfiguration let disclosureOptions = UICellAccessory.OutlineDisclosureOptions(style: .header) - cell.accessories = [.outlineDisclosure(options:disclosureOptions)] + cell.accessories = [.outlineDisclosure(options: disclosureOptions)] let background = UIBackgroundConfiguration.clear() cell.backgroundConfiguration = background } let cellRegistration = UICollectionView.CellRegistration { cell, indexPath, menuItem in - var content = UIListContentConfiguration.cell() - content.text = menuItem.title - cell.contentConfiguration = content + var contentConfiguration = cell.defaultContentConfiguration() + contentConfiguration.text = menuItem.title + + if let image = menuItem.imageName { + contentConfiguration.image = UIImage(systemName: image) + } + + cell.contentConfiguration = contentConfiguration let background = UIBackgroundConfiguration.clear() cell.backgroundConfiguration = background @@ -295,6 +322,14 @@ extension OutlineViewController: UICollectionViewDelegate { if let storyboardName = menuItem.storyboardName { pushOrPresentStoryboard(storyboardName: storyboardName) + + if navigationController!.traitCollection.userInterfaceIdiom == .mac { + if let windowScene = view.window?.windowScene { + if #available(iOS 15, *) { + windowScene.subtitle = menuItem.title + } + } + } } } diff --git a/UIKitCatalog/PointerInteractionButtonViewController.swift b/UIKitCatalog/PointerInteractionButtonViewController.swift new file mode 100755 index 0000000..b928346 --- /dev/null +++ b/UIKitCatalog/PointerInteractionButtonViewController.swift @@ -0,0 +1,168 @@ +/* +See LICENSE folder for this sample’s licensing information. + +Abstract: +A view controller that demonstrates how to intergrate pointer interactions to `UIButton`. +*/ + +import UIKit + +class PointerInteractionButtonViewController: BaseTableViewController { + + // Cell identifier for each button pointer table view cell. + enum PointerButtonKind: String, CaseIterable { + case buttonPointer + case buttonHighlight + case buttonLift + case buttonHover + case buttonCustom + } + + // The pointer effect kind to use for each button (corresponds to the button's view tag). + enum ButtonPointerEffectKind: Int { + case pointer = 1 + case highlight + case lift + case hover + case custom + } + + override func viewDidLoad() { + super.viewDidLoad() + + testCells.append(contentsOf: [ + CaseElement(title: "UIPointerEffect.automatic", + cellID: PointerButtonKind.buttonPointer.rawValue, + configHandler: configurePointerButton), + CaseElement(title: "UIPointerEffect.highlight", + cellID: PointerButtonKind.buttonHighlight.rawValue, + configHandler: configureHighlightButton), + CaseElement(title: "UIPointerEffect.lift", + cellID: PointerButtonKind.buttonLift.rawValue, + configHandler: configureLiftButton), + CaseElement(title: "UIPointerEffect.hover", + cellID: PointerButtonKind.buttonHover.rawValue, + configHandler: configureHoverButton), + CaseElement(title: "UIPointerEffect (custom)", + cellID: PointerButtonKind.buttonCustom.rawValue, + configHandler: configureCustomButton) + ]) + } + + // MARK: - Configurations + + func configurePointerButton(button: UIButton) { + button.pointerStyleProvider = defaultButtonProvider + } + + func configureHighlightButton(button: UIButton) { + button.pointerStyleProvider = highlightButtonProvider + } + + func configureLiftButton(button: UIButton) { + button.pointerStyleProvider = liftButtonProvider + } + + func configureHoverButton(button: UIButton) { + button.pointerStyleProvider = hoverButtonProvider + } + + func configureCustomButton(button: UIButton) { + button.pointerStyleProvider = customButtonProvider + } + + // MARK: Button Pointer Providers + + func defaultButtonProvider(button: UIButton, pointerEffect: UIPointerEffect, pointerShape: UIPointerShape) -> UIPointerStyle? { + var buttonPointerStyle: UIPointerStyle? = nil + + // Use the pointer effect's preview that's passed in. + let targetedPreview = pointerEffect.preview + + /** UIPointerEffect.automatic attempts to determine the appropriate effect for the given preview automatically. + The pointer effect has an automatic nature which adapts to the aspects of the button (background color, corner radius, size) + */ + let buttonPointerEffect = UIPointerEffect.automatic(targetedPreview) + buttonPointerStyle = UIPointerStyle(effect: buttonPointerEffect, shape: pointerShape) + return buttonPointerStyle + } + + func highlightButtonProvider(button: UIButton, pointerEffect: UIPointerEffect, pointerShape: UIPointerShape) -> UIPointerStyle? { + var buttonPointerStyle: UIPointerStyle? = nil + + // Use the pointer effect's preview that's passed in. + let targetedPreview = pointerEffect.preview + + // Pointer slides under the given view and morphs into the view's shape. + let buttonHighlightPointerEffect = UIPointerEffect.highlight(targetedPreview) + buttonPointerStyle = UIPointerStyle(effect: buttonHighlightPointerEffect, shape: pointerShape) + + return buttonPointerStyle + } + + func liftButtonProvider(button: UIButton, pointerEffect: UIPointerEffect, pointerShape: UIPointerShape) -> UIPointerStyle? { + var buttonPointerStyle: UIPointerStyle? = nil + + // Use the pointer effect's preview that's passed in. + let targetedPreview = pointerEffect.preview + + /** Pointer slides under the given view and disappears as the view scales up and gains a shadow. + Make the pointer shape’s bounds match the view’s frame so the highlight extends to the edges. + */ + let buttonLiftPointerEffect = UIPointerEffect.lift(targetedPreview) + let customPointerShape = UIPointerShape.path(UIBezierPath(roundedRect: button.bounds, cornerRadius: 6.0)) + buttonPointerStyle = UIPointerStyle(effect: buttonLiftPointerEffect, shape: customPointerShape) + + return buttonPointerStyle + } + + func hoverButtonProvider(button: UIButton, pointerEffect: UIPointerEffect, pointerShape: UIPointerShape) -> UIPointerStyle? { + var buttonPointerStyle: UIPointerStyle? = nil + + // Use the pointer effect's preview that's passed in. + let targetedPreview = pointerEffect.preview + + /** Pointer retains the system shape while over the given view. + Visual changes applied to the view are dictated by the effect's properties. + */ + let buttonHoverPointerEffect = + UIPointerEffect.hover(targetedPreview, preferredTintMode: .none, prefersShadow: true) + buttonPointerStyle = UIPointerStyle(effect: buttonHoverPointerEffect, shape: nil) + + return buttonPointerStyle + } + + func customButtonProvider(button: UIButton, pointerEffect: UIPointerEffect, pointerShape: UIPointerShape) -> UIPointerStyle? { + var buttonPointerStyle: UIPointerStyle? = nil + + /** Hover pointer with a custom triangle pointer shape. + Override the default UITargetedPreview with our own, make the visible path outset a little larger. + */ + let parameters = UIPreviewParameters() + parameters.visiblePath = UIBezierPath(rect: button.bounds.insetBy(dx: -15.0, dy: -15.0)) + let newTargetedPreview = UITargetedPreview(view: button, parameters: parameters) + + let buttonPointerEffect = + UIPointerEffect.hover(newTargetedPreview, preferredTintMode: .overlay, prefersShadow: false, prefersScaledContent: false) + + let customPointerShape = UIPointerShape.path(trianglePointerShape()) + buttonPointerStyle = UIPointerStyle(effect: buttonPointerEffect, shape: customPointerShape) + + return buttonPointerStyle + } + + // Return a triangle bezier path for the pointer's shape. + func trianglePointerShape() -> UIBezierPath { + let width = 20.0 + let height = 20.0 + let offset = 10.0 // Coordinate location to match up with the coordinate of default pointer shape. + + let pathView = UIBezierPath() + pathView.move(to: CGPoint(x: (width / 2) - offset, y: -offset)) + pathView.addLine(to: CGPoint(x: -offset, y: height - offset)) + pathView.addLine(to: CGPoint(x: width - offset, y: height - offset)) + pathView.close() + + return pathView + } +} diff --git a/UIKitCatalog/ProgressViewController.swift b/UIKitCatalog/ProgressViewController.swift index 0c46e20..60365d8 100755 --- a/UIKitCatalog/ProgressViewController.swift +++ b/UIKitCatalog/ProgressViewController.swift @@ -7,26 +7,25 @@ A view controller that demonstrates how to use `UIProgressView`. import UIKit -class ProgressViewController: UITableViewController { +class ProgressViewController: BaseTableViewController { + // Cell identifier for each progress view table view cell. + enum ProgressViewKind: String, CaseIterable { + case defaultProgress + case barProgress + case tintedProgress + } + // MARK: - Properties - @IBOutlet weak var defaultStyleProgressView: UIProgressView! + var observer: NSKeyValueObservation? - @IBOutlet weak var barStyleProgressView: UIProgressView! - - @IBOutlet weak var tintedProgressView: UIProgressView! - - @IBOutlet var progressViews: [UIProgressView]! - - var observer: NSKeyValueObservation? - - /** An `NSProgress` object who's `fractionCompleted` is observed using KVO to update - the `UIProgressView`s' `progress` properties. - */ - let progress = Progress(totalUnitCount: 10) + // An `NSProgress` object whose `fractionCompleted` is observed using KVO to update the `UIProgressView`s' `progress` properties. + let progress = Progress(totalUnitCount: 10) // A repeating timer that, when fired, updates the `NSProgress` object's `completedUnitCount` property. - var updateTimer: Timer? + var updateTimer: Timer? + + var progressViews = [UIProgressView]() // Accumulated progress views from all table cells for progress updating. // MARK: - Initialization @@ -34,37 +33,46 @@ class ProgressViewController: UITableViewController { super.init(coder: aDecoder) // Register as an observer of the `NSProgress`'s `fractionCompleted` property. - observer = progress.observe(\.fractionCompleted, options: [.new]) { (_, _) in - // Update the progress views. - for progressView in self.progressViews { + observer = progress.observe(\.fractionCompleted, options: [.new]) { (_, _) in + // Update the progress views. + for progressView in self.progressViews { progressView.setProgress(Float(self.progress.fractionCompleted), animated: true) - } - } + } + } } deinit { // Unregister as an observer of the `NSProgress`'s `fractionCompleted` property. observer?.invalidate() } - - // MARK: - View Life Cycle + // MARK: - View Life Cycle + override func viewDidLoad() { super.viewDidLoad() - configureDefaultStyleProgressView() - configureBarStyleProgressView() - configureTintedProgressView() + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("ProgressDefaultTitle", comment: ""), + cellID: ProgressViewKind.defaultProgress.rawValue, + configHandler: configureDefaultStyleProgressView), + CaseElement(title: NSLocalizedString("ProgressBarTitle", comment: ""), + cellID: ProgressViewKind.barProgress.rawValue, + configHandler: configureBarStyleProgressView) + ]) + + if traitCollection.userInterfaceIdiom != .mac { + // Tinted progress views available only on iOS. + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("ProgressTintedTitle", comment: ""), + cellID: ProgressViewKind.tintedProgress.rawValue, + configHandler: configureTintedProgressView) + ]) + } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - - // Reset the completed progress of the `UIProgressView`s. - for progressView in progressViews { - progressView.setProgress(0.0, animated: false) - } - + /** Reset the `completedUnitCount` of the `NSProgress` object and create a repeating timer to increment it over time. */ @@ -91,50 +99,34 @@ class ProgressViewController: UITableViewController { // MARK: - Configuration - func configureDefaultStyleProgressView() { - defaultStyleProgressView.progressViewStyle = .default + func configureDefaultStyleProgressView(_ progressView: UIProgressView) { + progressView.progressViewStyle = .default + + // Reset the completed progress of the `UIProgressView`s. + progressView.setProgress(0.0, animated: false) + + progressViews.append(progressView) } - func configureBarStyleProgressView() { - barStyleProgressView.progressViewStyle = .bar + func configureBarStyleProgressView(_ progressView: UIProgressView) { + progressView.progressViewStyle = .bar + + // Reset the completed progress of the `UIProgressView`s. + progressView.setProgress(0.0, animated: false) + + progressViews.append(progressView) } - func configureTintedProgressView() { - tintedProgressView.progressViewStyle = .default + func configureTintedProgressView(_ progressView: UIProgressView) { + progressView.progressViewStyle = .default - tintedProgressView.trackTintColor = UIColor.systemBlue - tintedProgressView.progressTintColor = UIColor.systemPurple + progressView.trackTintColor = UIColor.systemBlue + progressView.progressTintColor = UIColor.systemPurple + + // Reset the completed progress of the `UIProgressView`s. + progressView.setProgress(0.0, animated: false) + + progressViews.append(progressView) } - // MARK: - UITableViewDataSource - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - #if targetEnvironment(macCatalyst) - // Don't show tinted progress view for macOS, it does not exist. - if section == 2 { - return "" - } else { - return super.tableView(tableView, titleForHeaderInSection: section) - } - #else - return super.tableView(tableView, titleForHeaderInSection: section) - #endif - } - - // MARK: - UITableViewDelegate - - override func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - #if targetEnvironment(macCatalyst) - // Don't show tinted progress view for macOS, it does not exist. - if indexPath.section == 2 { - return 0 - } else { - return super.tableView(tableView, heightForRowAt: indexPath) - } - #else - return super.tableView(tableView, heightForRowAt: indexPath) - #endif - } - } diff --git a/UIKitCatalog/SceneDelegate.swift b/UIKitCatalog/SceneDelegate.swift index 8b62439..57c1c53 100755 --- a/UIKitCatalog/SceneDelegate.swift +++ b/UIKitCatalog/SceneDelegate.swift @@ -10,81 +10,79 @@ import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate, UISplitViewControllerDelegate { var window: UIWindow? - /** Applications should configure their UIWindow, and attach the UIWindow to the provided UIWindowScene scene. + /** Applications configure their UIWindow and attach the UIWindow to the provided UIWindowScene scene. Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - If using a storyboard file (as specified by the Info.plist key, UISceneStoryboardFile, - the window property will automatically be configured and attached to the windowScene. + If using a storyboard file, as specified by the Info.plist key `UISceneStoryboardFile`, + the window property automatically configures and attaches to the windowScene. - Remember to retain the SceneDelegate 's UIWindow. + Remember to retain the SceneDelegate's UIWindow. The recommended approach is for the SceneDelegate to retain the scene's window. */ func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - guard let windowScene = (scene as? UIWindowScene) else { return } + guard (scene as? UIWindowScene) != nil else { return } if let splitViewController = window!.rootViewController as? UISplitViewController { splitViewController.delegate = self - splitViewController.preferredDisplayMode = .oneBesideSecondary - splitViewController.presentsWithGesture = false - if let navController = splitViewController.viewControllers[1] as? UINavigationController { - // For the Mac, remove the title bar and navigation bar. - #if targetEnvironment(macCatalyst) - if let titlebar = windowScene.titlebar { - titlebar.titleVisibility = .hidden - titlebar.toolbar = nil - } - // Hide the navigation bar on the Mac. + // For the Mac, remove the navigation bar. + if navController.traitCollection.userInterfaceIdiom == .mac { navController.navigationBar.isHidden = true - #endif + } } } } - /** Called as the scene is being released by the system or on window close. - This occurs shortly after the scene enters the background, or when its session is discarded. - Release any resources associated with this scene that can be re-created the next time the scene connects. - The scene may re-connect later, as its session was not neccessarily discarded (see`application:didDiscardSceneSessions` instead). + /** Called by iOS when the system is releasing the scene or on window close. + This occurs shortly after the scene enters the background, or when discarding its session. + Release any resources for this scene that you can create the next time the scene connects. + The scene may reconnect later because the system doesn't necessarily discard its session + (see `application:didDiscardSceneSessions` instead). */ func sceneDidDisconnect(_ scene: UIScene) { } - /** Called as the scene transitions from the background to the foreground, - on window open or in iOS resume. - Use this method to undo the changes made on entering the background. + /** Called by iOS as the scene transitions from the background to the foreground, on window open, or on iOS resume. + Use this method to undo the changes that occur on entering the background. */ func sceneWillEnterForeground(_ scene: UIScene) { } - /** Called as the scene transitions from the foreground to the background. + /** Called by iOS as the scene transitions from the foreground to the background. Use this method to save data, release shared resources, and store enough scene-specific state information - to restore the scene back to its current state. + to restore the scene to its current state. */ func sceneDidEnterBackground(_ scene: UIScene) { } - /** Called when the scene "will move" from an active state to an inactive state, - on window close or in iOS enter background. - This may occur due to temporary interruptions (ex. an incoming phone call). + /** Called by iOS when the scene is about to move from an active state to an inactive state, on window close or on iOS enter background. + This may occur due to temporary interruptions (such as, an incoming phone call). */ func sceneWillResignActive(_ scene: UIScene) { } - /** Called when the scene "has moved" from an inactive state to an active state. - Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - Is called every time a scene becomes active, so setup your scene UI here. + /** Called by iOS when the scene after the scene moves from an inactive state to an active state. + Use this method to restart any paused tasks (or pending tasks) when the scene is inactive. + This is called every time a scene becomes active, so set up your scene UI here. */ func sceneDidBecomeActive(_ scene: UIScene) { } - // MARK: - UISplitViewControllerDelegate - - func splitViewController(_ splitViewController: UISplitViewController, - collapseSecondary secondaryViewController: UIViewController, - onto primaryViewController: UIViewController) -> Bool { - // Return true to prevent UIKit from applying its default behavior. - return true + func splitViewController(_ svc: UISplitViewController, + topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column) + -> UISplitViewController.Column { + return .secondary } + + func splitViewController(_ svc: UISplitViewController, + displayModeForExpandingToProposedDisplayMode proposedDisplayMode: UISplitViewController.DisplayMode) + -> UISplitViewController.DisplayMode { + if let navController = svc.viewControllers[0] as? UINavigationController { + navController.popToRootViewController(animated: false) + } + return .automatic + } + } diff --git a/UIKitCatalog/SegmentedControlViewController.swift b/UIKitCatalog/SegmentedControlViewController.swift index 865ee9f..59f2e7d 100755 --- a/UIKitCatalog/SegmentedControlViewController.swift +++ b/UIKitCatalog/SegmentedControlViewController.swift @@ -7,76 +7,117 @@ A view controller that demonstrates how to use `UISegmentedControl`. import UIKit -class SegmentedControlViewController: UITableViewController { - // MARK: - Properties - - @IBOutlet weak var defaultSegmentedControl: UISegmentedControl! - @IBOutlet weak var tintedSegmentedControl: UISegmentedControl! - @IBOutlet weak var customSegmentsSegmentedControl: UISegmentedControl! - @IBOutlet weak var customBackgroundSegmentedControl: UISegmentedControl! - @IBOutlet weak var actionBasedSegmentedControl: UISegmentedControl! +class SegmentedControlViewController: BaseTableViewController { + + // Cell identifier for each segmented control table view cell. + enum SegmentKind: String, CaseIterable { + case segmentDefault + case segmentTinted + case segmentCustom + case segmentCustomBackground + case segmentAction + } // MARK: - View Life Cycle override func viewDidLoad() { super.viewDidLoad() - configureDefaultSegmentedControl() - configureTintedSegmentedControl() - configureCustomSegmentsSegmentedControl() - configureCustomBackgroundSegmentedControl() - configureActionBasedSegmentedControl() + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("DefaultTitle", comment: ""), + cellID: SegmentKind.segmentDefault.rawValue, + configHandler: configureDefaultSegmentedControl), + CaseElement(title: NSLocalizedString("CustomSegmentsTitle", comment: ""), + cellID: SegmentKind.segmentCustom.rawValue, + configHandler: configureCustomSegmentsSegmentedControl), + CaseElement(title: NSLocalizedString("CustomBackgroundTitle", comment: ""), + cellID: SegmentKind.segmentCustomBackground.rawValue, + configHandler: configureCustomBackgroundSegmentedControl), + CaseElement(title: NSLocalizedString("ActionBasedTitle", comment: ""), + cellID: SegmentKind.segmentAction.rawValue, + configHandler: configureActionBasedSegmentedControl) + ]) + if self.traitCollection.userInterfaceIdiom != .mac { + // Tinted segmented control is only available on iOS. + testCells.append(contentsOf: [ + CaseElement(title: "Tinted", + cellID: SegmentKind.segmentTinted.rawValue, + configHandler: configureTintedSegmentedControl) + ]) + } } // MARK: - Configuration - func configureDefaultSegmentedControl() { + func configureDefaultSegmentedControl(_ segmentedControl: UISegmentedControl) { // As a demonstration, disable the first segment. - defaultSegmentedControl.setEnabled(false, forSegmentAt: 0) + segmentedControl.setEnabled(false, forSegmentAt: 0) - defaultSegmentedControl.addTarget(self, action: #selector(SegmentedControlViewController.selectedSegmentDidChange(_:)), for: .valueChanged) + segmentedControl.addTarget(self, action: #selector(SegmentedControlViewController.selectedSegmentDidChange(_:)), for: .valueChanged) } - func configureTintedSegmentedControl() { - // Use a dynamic tinted color (separate one for Light Appearance and separate one for Dark Appearance). - tintedSegmentedControl.selectedSegmentTintColor = UIColor(named: "tinted_segmented_control")! + func configureTintedSegmentedControl(_ segmentedControl: UISegmentedControl) { + // Use a dynamic tinted "green" color (separate one for Light Appearance and separate one for Dark Appearance). + segmentedControl.selectedSegmentTintColor = UIColor(named: "tinted_segmented_control")! + segmentedControl.selectedSegmentIndex = 1 - tintedSegmentedControl.selectedSegmentIndex = 1 - - tintedSegmentedControl.addTarget(self, action: #selector(SegmentedControlViewController.selectedSegmentDidChange(_:)), for: .valueChanged) + segmentedControl.addTarget(self, action: #selector(SegmentedControlViewController.selectedSegmentDidChange(_:)), for: .valueChanged) } - func configureCustomSegmentsSegmentedControl() { + func configureCustomSegmentsSegmentedControl(_ segmentedControl: UISegmentedControl) { let airplaneImage = UIImage(systemName: "airplane") airplaneImage?.accessibilityLabel = NSLocalizedString("Airplane", comment: "") - customSegmentsSegmentedControl.setImage(airplaneImage, forSegmentAt: 0) + segmentedControl.setImage(airplaneImage, forSegmentAt: 0) let giftImage = UIImage(systemName: "gift") giftImage?.accessibilityLabel = NSLocalizedString("Gift", comment: "") - customSegmentsSegmentedControl.setImage(giftImage, forSegmentAt: 1) + segmentedControl.setImage(giftImage, forSegmentAt: 1) let burstImage = UIImage(systemName: "burst") burstImage?.accessibilityLabel = NSLocalizedString("Burst", comment: "") - customSegmentsSegmentedControl.setImage(burstImage, forSegmentAt: 2) + segmentedControl.setImage(burstImage, forSegmentAt: 2) - customSegmentsSegmentedControl.selectedSegmentIndex = 0 + segmentedControl.selectedSegmentIndex = 0 - customSegmentsSegmentedControl.addTarget(self, - action: #selector(SegmentedControlViewController.selectedSegmentDidChange(_:)), - for: .valueChanged) + segmentedControl.addTarget(self, action: #selector(SegmentedControlViewController.selectedSegmentDidChange(_:)), for: .valueChanged) } - func configureCustomBackgroundSegmentedControl() { + // Utility function to resize an image to a particular size. + func scaledImage(_ image: UIImage, scaledToSize newSize: CGSize) -> UIImage { + UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0) + image.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)) + let newImage = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + return newImage + } + + // Configure the segmented control with a background image, dividers, and custom font. + // The background image first needs to be sized to match the control's size. + // + func configureCustomBackgroundSegmentedControl(_ placeHolderView: UIView) { + let customBackgroundSegmentedControl = + UISegmentedControl(items: [NSLocalizedString("CheckTitle", comment: ""), + NSLocalizedString("SearchTitle", comment: ""), + NSLocalizedString("ToolsTitle", comment: "")]) customBackgroundSegmentedControl.selectedSegmentIndex = 2 - + + // Place this custom segmented control within the placeholder view. + customBackgroundSegmentedControl.frame.size.width = placeHolderView.frame.size.width + customBackgroundSegmentedControl.frame.origin.y = + (placeHolderView.bounds.size.height - customBackgroundSegmentedControl.bounds.size.height) / 2 + placeHolderView.addSubview(customBackgroundSegmentedControl) + // Set the background images for each control state. - let normalSegmentBackgroundImage = UIImage(named: "stepper_and_segment_background") - customBackgroundSegmentedControl.setBackgroundImage(normalSegmentBackgroundImage, for: .normal, barMetrics: .default) - - let disabledSegmentBackgroundImage = UIImage(named: "stepper_and_segment_background_disabled") + let normalSegmentBackgroundImage = UIImage(named: "background") + // Size the background image to match the bounds of the segmented control. + let backgroundImageSize = customBackgroundSegmentedControl.bounds.size + let newBackgroundImageSize = scaledImage(normalSegmentBackgroundImage!, scaledToSize: backgroundImageSize) + customBackgroundSegmentedControl.setBackgroundImage(newBackgroundImageSize, for: .normal, barMetrics: .default) + + let disabledSegmentBackgroundImage = UIImage(named: "background_disabled") customBackgroundSegmentedControl.setBackgroundImage(disabledSegmentBackgroundImage, for: .disabled, barMetrics: .default) - let highlightedSegmentBackgroundImage = UIImage(named: "stepper_and_segment_background_highlighted") + let highlightedSegmentBackgroundImage = UIImage(named: "background_highlighted") customBackgroundSegmentedControl.setBackgroundImage(highlightedSegmentBackgroundImage, for: .highlighted, barMetrics: .default) // Set the divider image. @@ -87,9 +128,7 @@ class SegmentedControlViewController: UITableViewController { barMetrics: .default) // Create a font to use for the attributed title, for both normal and highlighted states. - let captionFontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .caption1) - let font = UIFont(descriptor: captionFontDescriptor, size: 0) - + let font = UIFont(descriptor: UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body), size: 0) let normalTextAttributes = [ NSAttributedString.Key.foregroundColor: UIColor.systemPurple, NSAttributedString.Key.font: font @@ -107,60 +146,44 @@ class SegmentedControlViewController: UITableViewController { for: .valueChanged) } - func configureActionBasedSegmentedControl() { - actionBasedSegmentedControl.selectedSegmentIndex = 0 + func configureActionBasedSegmentedControl(_ segmentedControl: UISegmentedControl) { + segmentedControl.selectedSegmentIndex = 0 let firstAction = UIAction(title: NSLocalizedString("CheckTitle", comment: "")) { action in Swift.debugPrint("Segment Action '\(action.title)'") } - actionBasedSegmentedControl.setAction(firstAction, forSegmentAt: 0) + segmentedControl.setAction(firstAction, forSegmentAt: 0) let secondAction = UIAction(title: NSLocalizedString("SearchTitle", comment: "")) { action in Swift.debugPrint("Segment Action '\(action.title)'") } - actionBasedSegmentedControl.setAction(secondAction, forSegmentAt: 1) + segmentedControl.setAction(secondAction, forSegmentAt: 1) let thirdAction = UIAction(title: NSLocalizedString("ToolsTitle", comment: "")) { action in Swift.debugPrint("Segment Action '\(action.title)'") } - actionBasedSegmentedControl.setAction(thirdAction, forSegmentAt: 2) + segmentedControl.setAction(thirdAction, forSegmentAt: 2) } - + // MARK: - Actions @objc func selectedSegmentDidChange(_ segmentedControl: UISegmentedControl) { - print("The selected segment changed for: \(segmentedControl).") + Swift.debugPrint("The selected segment: \(segmentedControl.selectedSegmentIndex).") } // MARK: - UITableViewDataSource - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - #if targetEnvironment(macCatalyst) - // Don't show tinted segmented control for macOS, it does not exist. - if section == 1 { - return "" - } else { - return super.tableView(tableView, titleForHeaderInSection: section) + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cellTest = testCells[indexPath.section] + let cell = tableView.dequeueReusableCell(withIdentifier: cellTest.cellID, for: indexPath) + if let segementedControl = cellTest.targetView(cell) as? UISegmentedControl { + cellTest.configHandler(segementedControl) + } else if let placeHolderView = cellTest.targetView(cell) { + // The only non-segmented control cell has a placeholder UIView (for adding one as a subview). + cellTest.configHandler(placeHolderView) } - #else - return super.tableView(tableView, titleForHeaderInSection: section) - #endif - } - - // MARK: - UITableViewDelegate - - override func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - #if targetEnvironment(macCatalyst) - // Don't show tinted segmented control for macOS, it does not exist. - if indexPath.section == 1 { - return 0 - } else { - return super.tableView(tableView, heightForRowAt: indexPath) - } - #else - return super.tableView(tableView, heightForRowAt: indexPath) - #endif + return cell } + } diff --git a/UIKitCatalog/SliderViewController.swift b/UIKitCatalog/SliderViewController.swift index a357fba..f1b3bb9 100755 --- a/UIKitCatalog/SliderViewController.swift +++ b/UIKitCatalog/SliderViewController.swift @@ -7,98 +7,139 @@ A view controller that demonstrates how to use `UISlider`. import UIKit -class SliderViewController: UITableViewController { - // MARK: - Properties +class SliderViewController: BaseTableViewController { + // Cell identifier for each slider table view cell. + enum SliderKind: String, CaseIterable { + case sliderDefault + case sliderTinted + case sliderCustom + case sliderMaxMinImage + } - @IBOutlet weak var defaultSlider: UISlider! - @IBOutlet weak var tintedSlider: UISlider! - @IBOutlet weak var customSlider: UISlider! - @IBOutlet weak var minMaxImageSlider: UISlider! - // MARK: - View Life Cycle - + override func viewDidLoad() { super.viewDidLoad() - configureDefaultSlider() - #if !targetEnvironment(macCatalyst) - /** Only show for the first table cell (default slider). - Because this sample has "Optimize Interface for Mac" turned on - - UISlider class: tinted, custom, and max/min image, are not supported when running Mac Catalyst apps in the Mac idiom. - */ - configureTintedSlider() - configureCustomSlider() - configureMinMaxImageSlider() - #endif + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("DefaultTitle", comment: ""), + cellID: SliderKind.sliderDefault.rawValue, + configHandler: configureDefaultSlider) + ]) + + if #available(iOS 15, *) { + // These cases require iOS 15 or later when running on Mac Catalyst. + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("CustomTitle", comment: ""), + cellID: SliderKind.sliderCustom.rawValue, + configHandler: configureCustomSlider) + ]) + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("MinMaxImagesTitle", comment: ""), + cellID: SliderKind.sliderMaxMinImage.rawValue, + configHandler: configureMinMaxImageSlider) + ]) + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("TintedTitle", comment: ""), + cellID: SliderKind.sliderTinted.rawValue, + configHandler: configureTintedSlider) + ]) + } } // MARK: - Configuration - - override func numberOfSections(in tableView: UITableView) -> Int { - #if targetEnvironment(macCatalyst) - /** Only show for the first table cell (default slider). - Because this sample has "Optimize Interface for Mac" turned on - - UISlider class: tinted, custom, and max/min image, are not supported when running Mac Catalyst apps in the Mac idiom. + + func configureDefaultSlider(_ slider: UISlider) { + slider.minimumValue = 0 + slider.maximumValue = 100 + slider.value = 42 + slider.isContinuous = true + + slider.addTarget(self, action: #selector(SliderViewController.sliderValueDidChange(_:)), for: .valueChanged) + } + + @available(iOS 15.0, *) + func configureTintedSlider(slider: UISlider) { + /** To keep the look the same betwen iOS and macOS: + For minimumTrackTintColor, maximumTrackTintColor to work in Mac Catalyst, use UIBehavioralStyle as ".pad", + Available in macOS 12 or later (Mac Catalyst 15.0 or later). + Use this for controls that need to look the same between iOS and macOS. */ - return 1 - #else - return super.numberOfSections(in: tableView) - #endif - } - - func configureDefaultSlider() { - defaultSlider.minimumValue = 0 - defaultSlider.maximumValue = 100 - defaultSlider.value = 42 - defaultSlider.isContinuous = true + if traitCollection.userInterfaceIdiom == .mac { + slider.preferredBehavioralStyle = .pad + } - defaultSlider.addTarget(self, - action: #selector(SliderViewController.sliderValueDidChange(_:)), - for: .valueChanged) + slider.minimumTrackTintColor = UIColor.systemBlue + slider.maximumTrackTintColor = UIColor.systemPurple + + slider.addTarget(self, action: #selector(SliderViewController.sliderValueDidChange(_:)), for: .valueChanged) } - func configureTintedSlider() { - tintedSlider.minimumTrackTintColor = UIColor.systemBlue - tintedSlider.maximumTrackTintColor = UIColor.systemPurple - - tintedSlider.addTarget(self, - action: #selector(SliderViewController.sliderValueDidChange(_:)), - for: .valueChanged) - } - - func configureCustomSlider() { + @available(iOS 15.0, *) + func configureCustomSlider(slider: UISlider) { + /** To keep the look the same betwen iOS and macOS: + For setMinimumTrackImage, setMaximumTrackImage, setThumbImage to work in Mac Catalyst, use UIBehavioralStyle as ".pad", + Available in macOS 12 or later (Mac Catalyst 15.0 or later). + Use this for controls that need to look the same between iOS and macOS. + */ + if traitCollection.userInterfaceIdiom == .mac { + slider.preferredBehavioralStyle = .pad + } + let leftTrackImage = UIImage(named: "slider_blue_track") - customSlider.setMinimumTrackImage(leftTrackImage, for: .normal) + slider.setMinimumTrackImage(leftTrackImage, for: .normal) let rightTrackImage = UIImage(named: "slider_green_track") - customSlider.setMaximumTrackImage(rightTrackImage, for: .normal) + slider.setMaximumTrackImage(rightTrackImage, for: .normal) // Set the sliding thumb image (normal and highlighted). - let thumbImageConfig = UIImage.SymbolConfiguration(scale: .large) + // + // For fun, choose a different image symbol configuraton for the thumb's image between macOS and iOS. + var thumbImageConfig: UIImage.SymbolConfiguration + if slider.traitCollection.userInterfaceIdiom == .mac { + thumbImageConfig = UIImage.SymbolConfiguration(scale: .large) + } else { + thumbImageConfig = UIImage.SymbolConfiguration(pointSize: 30, weight: .heavy, scale: .large) + } let thumbImage = UIImage(systemName: "circle.fill", withConfiguration: thumbImageConfig) - customSlider.setThumbImage(thumbImage, for: .normal) + slider.setThumbImage(thumbImage, for: .normal) + let thumbImageHighlighted = UIImage(systemName: "circle", withConfiguration: thumbImageConfig) - customSlider.setThumbImage(thumbImageHighlighted, for: .highlighted) + slider.setThumbImage(thumbImageHighlighted, for: .highlighted) - customSlider.minimumValue = 0 - customSlider.maximumValue = 100 - customSlider.isContinuous = false - customSlider.value = 84 + // Set the rest of the slider's attributes. + slider.minimumValue = 0 + slider.maximumValue = 100 + slider.isContinuous = false + slider.value = 84 - customSlider.addTarget(self, action: #selector(SliderViewController.sliderValueDidChange(_:)), for: .valueChanged) + slider.addTarget(self, action: #selector(SliderViewController.sliderValueDidChange(_:)), for: .valueChanged) } - func configureMinMaxImageSlider() { - minMaxImageSlider.minimumValueImage = UIImage(systemName: "tortoise") - minMaxImageSlider.maximumValueImage = UIImage(systemName: "hare") + func configureMinMaxImageSlider(slider: UISlider) { + /** To keep the look the same betwen iOS and macOS: + For setMinimumValueImage, setMaximumValueImage to work in Mac Catalyst, use UIBehavioralStyle as ".pad", + Available in macOS 12 or later (Mac Catalyst 15.0 or later). + Use this for controls that need to look the same between iOS and macOS. + */ + if #available(iOS 15, *) { + if traitCollection.userInterfaceIdiom == .mac { + slider.preferredBehavioralStyle = .pad + } + } - minMaxImageSlider.addTarget(self, action: #selector(SliderViewController.sliderValueDidChange(_:)), for: .valueChanged) + slider.minimumValueImage = UIImage(systemName: "tortoise") + slider.maximumValueImage = UIImage(systemName: "hare") + + slider.addTarget(self, action: #selector(SliderViewController.sliderValueDidChange(_:)), for: .valueChanged) } // MARK: - Actions @objc func sliderValueDidChange(_ slider: UISlider) { - print("A slider changed its value: \(slider).") + let formattedValue = String(format: "%.2f", slider.value) + Swift.debugPrint("Slider changed its value: \(formattedValue)") } + } diff --git a/UIKitCatalog/StackViewController.swift b/UIKitCatalog/StackViewController.swift index 5f10276..b8859f2 100755 --- a/UIKitCatalog/StackViewController.swift +++ b/UIKitCatalog/StackViewController.swift @@ -11,13 +11,9 @@ class StackViewController: UIViewController { // MARK: - Properties @IBOutlet var furtherDetailStackView: UIStackView! - @IBOutlet var plusButton: UIButton! - @IBOutlet var addRemoveExampleStackView: UIStackView! - @IBOutlet var addArrangedViewButton: UIButton! - @IBOutlet var removeArrangedViewButton: UIButton! let maximumArrangedSubviewCount = 3 diff --git a/UIKitCatalog/StepperViewController.swift b/UIKitCatalog/StepperViewController.swift index ae587f4..4b4b417 100755 --- a/UIKitCatalog/StepperViewController.swift +++ b/UIKitCatalog/StepperViewController.swift @@ -7,106 +7,91 @@ A view controller that demonstrates how to use `UIStepper`. import UIKit -class StepperViewController: UITableViewController { - // MARK: - Properties - - @IBOutlet weak var defaultStepper: UIStepper! - - @IBOutlet weak var tintedStepper: UIStepper! +class StepperViewController: BaseTableViewController { - @IBOutlet weak var customStepper: UIStepper! - - @IBOutlet weak var defaultStepperLabel: UILabel! + // Cell identifier for each stepper table view cell. + enum StepperKind: String, CaseIterable { + case defaultStepper + case tintedStepper + case customStepper + } - @IBOutlet weak var tintedStepperLabel: UILabel! - - @IBOutlet weak var customStepperLabel: UILabel! - - // MARK: - View Life Cycle - override func viewDidLoad() { super.viewDidLoad() - configureDefaultStepper() - configureTintedStepper() - configureCustomStepper() + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("DefaultStepperTitle", comment: ""), + cellID: StepperKind.defaultStepper.rawValue, + configHandler: configureDefaultStepper), + CaseElement(title: NSLocalizedString("TintedStepperTitle", comment: ""), + cellID: StepperKind.tintedStepper.rawValue, + configHandler: configureTintedStepper), + CaseElement(title: NSLocalizedString("CustomStepperTitle", comment: ""), + cellID: StepperKind.customStepper.rawValue, + configHandler: configureCustomStepper) + ]) } // MARK: - Configuration - - override func numberOfSections(in tableView: UITableView) -> Int { - #if targetEnvironment(macCatalyst) - // Only show for the first table cell (default stepper). - // Tinted and Custom steppers do not exist in macOS. - return 1 - #else - return super.numberOfSections(in: tableView) - #endif - } - func configureDefaultStepper() { - defaultStepper.value = 0 - defaultStepper.minimumValue = 0 - defaultStepper.maximumValue = 10 - defaultStepper.stepValue = 1 + func configureDefaultStepper(stepper: UIStepper) { + // Setup the stepper range 0 to 10, initial value 0, increment/decrement factor of 1. + stepper.value = 0 + stepper.minimumValue = 0 + stepper.maximumValue = 10 + stepper.stepValue = 1 - defaultStepperLabel.text = "\(Int(defaultStepper.value))" - defaultStepper.addTarget(self, - action: #selector(StepperViewController.stepperValueDidChange(_:)), - for: .valueChanged) + stepper.addTarget(self, + action: #selector(StepperViewController.stepperValueDidChange(_:)), + for: .valueChanged) } - func configureTintedStepper() { - tintedStepper.tintColor = UIColor(named: "tinted_stepper_control")! - tintedStepper.setDecrementImage(tintedStepper.decrementImage(for: .normal), for: .normal) - tintedStepper.setIncrementImage(tintedStepper.incrementImage(for: .normal), for: .normal) + func configureTintedStepper(stepper: UIStepper) { + // Setup the stepper range 0 to 20, initial value 20, increment/decrement factor of 1. + stepper.value = 20 + stepper.minimumValue = 0 + stepper.maximumValue = 20 + stepper.stepValue = 1 - tintedStepperLabel.text = "\(Int(tintedStepper.value))" - tintedStepper.addTarget(self, - action: #selector(StepperViewController.stepperValueDidChange(_:)), - for: .valueChanged) + stepper.tintColor = UIColor(named: "tinted_stepper_control")! + stepper.setDecrementImage(stepper.decrementImage(for: .normal), for: .normal) + stepper.setIncrementImage(stepper.incrementImage(for: .normal), for: .normal) + + stepper.addTarget(self, + action: #selector(StepperViewController.stepperValueDidChange(_:)), + for: .valueChanged) } - func configureCustomStepper() { + func configureCustomStepper(stepper: UIStepper) { // Set the background image. - let stepperBackgroundImage = UIImage(named: "stepper_and_segment_background") - customStepper.setBackgroundImage(stepperBackgroundImage, for: .normal) + let stepperBackgroundImage = UIImage(named: "background") + stepper.setBackgroundImage(stepperBackgroundImage, for: .normal) - let stepperHighlightedBackgroundImage = UIImage(named: "stepper_and_segment_background_highlighted") - customStepper.setBackgroundImage(stepperHighlightedBackgroundImage, for: .highlighted) + let stepperHighlightedBackgroundImage = UIImage(named: "background_highlighted") + stepper.setBackgroundImage(stepperHighlightedBackgroundImage, for: .highlighted) - let stepperDisabledBackgroundImage = UIImage(named: "stepper_and_segment_background_disabled") - customStepper.setBackgroundImage(stepperDisabledBackgroundImage, for: .disabled) + let stepperDisabledBackgroundImage = UIImage(named: "background_disabled") + stepper.setBackgroundImage(stepperDisabledBackgroundImage, for: .disabled) // Set the image which will be painted in between the two stepper segments. It depends on the states of both segments. let stepperSegmentDividerImage = UIImage(named: "stepper_and_segment_divider") - customStepper.setDividerImage(stepperSegmentDividerImage, forLeftSegmentState: .normal, rightSegmentState: .normal) + stepper.setDividerImage(stepperSegmentDividerImage, forLeftSegmentState: .normal, rightSegmentState: .normal) // Set the image for the + button. - let stepperIncrementImage = UIImage(named: "stepper_increment") - customStepper.setIncrementImage(stepperIncrementImage, for: .normal) + let stepperIncrementImage = UIImage(systemName: "plus") + stepper.setIncrementImage(stepperIncrementImage, for: .normal) // Set the image for the - button. - let stepperDecrementImage = UIImage(named: "stepper_decrement") - customStepper.setDecrementImage(stepperDecrementImage, for: .normal) + let stepperDecrementImage = UIImage(systemName: "minus") + stepper.setDecrementImage(stepperDecrementImage, for: .normal) - customStepperLabel.text = "\(Int(customStepper.value))" - customStepper.addTarget(self, action: #selector(StepperViewController.stepperValueDidChange(_:)), for: .valueChanged) + stepper.addTarget(self, action: #selector(StepperViewController.stepperValueDidChange(_:)), for: .valueChanged) } // MARK: - Actions @objc func stepperValueDidChange(_ stepper: UIStepper) { - print("A stepper changed its value: \(stepper).") - - // A mapping from a stepper to its associated label. - let stepperMapping = [ - defaultStepper: defaultStepperLabel, - tintedStepper: tintedStepperLabel, - customStepper: customStepperLabel - ] - - stepperMapping[stepper]!?.text = "\(Int(stepper.value))" + Swift.debugPrint("A stepper changed its value: \(stepper.value).") } } diff --git a/UIKitCatalog/SwitchViewController.swift b/UIKitCatalog/SwitchViewController.swift index 84cd671..4200d2b 100755 --- a/UIKitCatalog/SwitchViewController.swift +++ b/UIKitCatalog/SwitchViewController.swift @@ -7,106 +7,85 @@ A view controller that demonstrates how to use `UISwitch`. import UIKit -class SwitchViewController: UITableViewController { - // MARK: - Properties +class SwitchViewController: BaseTableViewController { - @IBOutlet weak var defaultSwitch: UISwitch! - @IBOutlet weak var checkBoxSwitch: UISwitch! - @IBOutlet weak var tintedSwitch: UISwitch! - - // MARK: - View Life Cycle + // Cell identifier for each switch table view cell. + enum SwitchKind: String, CaseIterable { + case defaultSwitch + case checkBoxSwitch + case tintedSwitch + } override func viewDidLoad() { super.viewDidLoad() - configureDefaultSwitch() - configureCheckboxSwitch() // macOS only. - configureTintedSwitch() // iOS only. + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("DefaultSwitchTitle", comment: ""), + cellID: SwitchKind.defaultSwitch.rawValue, + configHandler: configureDefaultSwitch) + ]) + + // Checkbox switch is available only when running on macOS. + if navigationController!.traitCollection.userInterfaceIdiom == .mac { + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("CheckboxSwitchTitle", comment: ""), + cellID: SwitchKind.checkBoxSwitch.rawValue, + configHandler: configureCheckboxSwitch) + ]) + } + + // Tinted switch is available only when running on iOS. + if navigationController!.traitCollection.userInterfaceIdiom != .mac { + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("TintedSwitchTitle", comment: ""), + cellID: SwitchKind.tintedSwitch.rawValue, + configHandler: configureTintedSwitch) + ]) + } } // MARK: - Configuration - func configureDefaultSwitch() { - defaultSwitch.setOn(true, animated: false) - defaultSwitch.preferredStyle = .sliding + func configureDefaultSwitch(_ switchControl: UISwitch) { + switchControl.setOn(true, animated: false) + switchControl.preferredStyle = .sliding - defaultSwitch.addTarget(self, action: #selector(SwitchViewController.switchValueDidChange(_:)), for: .valueChanged) + switchControl.addTarget(self, + action: #selector(SwitchViewController.switchValueDidChange(_:)), + for: .valueChanged) } - func configureCheckboxSwitch() { - checkBoxSwitch.setOn(true, animated: false) + func configureCheckboxSwitch(_ switchControl: UISwitch) { + switchControl.setOn(true, animated: false) - checkBoxSwitch.addTarget(self, action: #selector(SwitchViewController.switchValueDidChange(_:)), for: .valueChanged) + switchControl.addTarget(self, + action: #selector(SwitchViewController.switchValueDidChange(_:)), + for: .valueChanged) // On the Mac, make sure this control take on the apperance of a checkbox with a title. if traitCollection.userInterfaceIdiom == .mac { - checkBoxSwitch.preferredStyle = .checkbox + switchControl.preferredStyle = .checkbox // Title on a UISwitch is only supported when running Catalyst apps in the Mac Idiom. - checkBoxSwitch.title = NSLocalizedString("SwitchTitle", comment: "") + switchControl.title = NSLocalizedString("SwitchTitle", comment: "") } } - func configureTintedSwitch() { - tintedSwitch.tintColor = UIColor.systemBlue - tintedSwitch.onTintColor = UIColor.systemGreen - tintedSwitch.thumbTintColor = UIColor.systemPurple + func configureTintedSwitch(_ switchControl: UISwitch) { + switchControl.tintColor = UIColor.systemBlue + switchControl.onTintColor = UIColor.systemGreen + switchControl.thumbTintColor = UIColor.systemPurple - tintedSwitch.addTarget(self, action: #selector(SwitchViewController.switchValueDidChange(_:)), for: .valueChanged) - - // Note that on the Mac, tinted switches are not possible, so we hide the tinted one. - if traitCollection.userInterfaceIdiom == .mac { - tintedSwitch.isHidden = true - } + switchControl.addTarget(self, + action: #selector(SwitchViewController.switchValueDidChange(_:)), + for: .valueChanged) } - // MARK: - UITableViewDataSource - - override func numberOfSections(in tableView: UITableView) -> Int { - return 3 - } - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - #if targetEnvironment(macCatalyst) - // Don't show tinted switch control for macOS, it does not exist. - if section == 2 { - return "" - } else { - return super.tableView(tableView, titleForHeaderInSection: section) - } - #else - // Don't show checkbox switch control for iOS, it does not exist. - if section == 1 { - return "" - } else { - return super.tableView(tableView, titleForHeaderInSection: section) - } - #endif - } - - override func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - #if targetEnvironment(macCatalyst) - // Don't show tinted switch control for macOS, it does not exist. - if indexPath.section == 2 { - return 0 - } else { - return super.tableView(tableView, heightForRowAt: indexPath) - } - #else - // Don't show checkbox switch control for iOS, it does not exist. - if indexPath.section == 1 { - return 0 - } else { - return super.tableView(tableView, heightForRowAt: indexPath) - } - #endif - } - // MARK: - Actions @objc func switchValueDidChange(_ aSwitch: UISwitch) { - print("A switch changed its value: \(aSwitch).") + Swift.debugPrint("A switch changed its value: \(aSwitch.isOn).") } + } diff --git a/UIKitCatalog/SymbolViewController.swift b/UIKitCatalog/SymbolViewController.swift new file mode 100755 index 0000000..5525304 --- /dev/null +++ b/UIKitCatalog/SymbolViewController.swift @@ -0,0 +1,103 @@ +/* +See LICENSE folder for this sample’s licensing information. + +Abstract: +A view controller that demonstrates how to use SF Symbols. +*/ + +import UIKit + +class SymbolViewController: BaseTableViewController { + + // Cell identifier for each SF Symbol table view cell. + enum SymbolKind: String, CaseIterable { + case plainSymbol + case tintedSymbol + case largeSizeSymbol + case hierarchicalColorSymbol + case paletteColorsSymbol + case preferringMultiColorSymbol + } + + override func viewDidLoad() { + super.viewDidLoad() + + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("PlainSymbolTitle", comment: ""), + cellID: SymbolKind.plainSymbol.rawValue, + configHandler: configurePlainSymbol), + CaseElement(title: NSLocalizedString("TintedSymbolTitle", comment: ""), + cellID: SymbolKind.tintedSymbol.rawValue, + configHandler: configureTintedSymbol), + CaseElement(title: NSLocalizedString("LargeSymbolTitle", comment: ""), + cellID: SymbolKind.largeSizeSymbol.rawValue, + configHandler: configureLargeSizeSymbol) + ]) + + if #available(iOS 15, *) { + // These type SF Sybols, and variants are available on iOS 15, Mac Catalyst 15 or later. + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("HierarchicalSymbolTitle", comment: ""), + cellID: SymbolKind.hierarchicalColorSymbol.rawValue, + configHandler: configureHierarchicalSymbol), + CaseElement(title: NSLocalizedString("PaletteSymbolTitle", comment: ""), + cellID: SymbolKind.paletteColorsSymbol.rawValue, + configHandler: configurePaletteColorsSymbol), + CaseElement(title: NSLocalizedString("PreferringMultiColorSymbolTitle", comment: ""), + cellID: SymbolKind.preferringMultiColorSymbol.rawValue, + configHandler: configurePreferringMultiColorSymbol) + ]) + } + } + + // MARK: - UITableViewDataSource + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + let cellTest = testCells[indexPath.section] + let cell = tableView.dequeueReusableCell(withIdentifier: cellTest.cellID) + return cell!.contentView.bounds.size.height + } + + // MARK: - Configuration + + func configurePlainSymbol(_ imageView: UIImageView) { + let image = UIImage(systemName: "cloud.sun.rain.fill") + imageView.image = image + } + + func configureTintedSymbol(_ imageView: UIImageView) { + let image = UIImage(systemName: "cloud.sun.rain.fill") + imageView.image = image + imageView.tintColor = .systemPurple + } + + func configureLargeSizeSymbol(_ imageView: UIImageView) { + let image = UIImage(systemName: "cloud.sun.rain.fill") + imageView.image = image + let symbolConfig = UIImage.SymbolConfiguration(pointSize: 32, weight: .heavy, scale: .large) + imageView.preferredSymbolConfiguration = symbolConfig + } + + func configureHierarchicalSymbol(_ imageView: UIImageView) { + let imageConfig = UIImage.SymbolConfiguration(hierarchicalColor: UIColor.systemRed) + let hierarchicalSymbol = UIImage(systemName: "cloud.sun.rain.fill") + imageView.image = hierarchicalSymbol + imageView.preferredSymbolConfiguration = imageConfig + } + + func configurePaletteColorsSymbol(_ imageView: UIImageView) { + let palleteSymbolConfig = UIImage.SymbolConfiguration(paletteColors: [UIColor.systemRed, UIColor.systemOrange, UIColor.systemYellow]) + let palleteSymbol = UIImage(systemName: "battery.100.bolt") + imageView.image = palleteSymbol + imageView.backgroundColor = UIColor.darkText + imageView.preferredSymbolConfiguration = palleteSymbolConfig + } + + func configurePreferringMultiColorSymbol(_ imageView: UIImageView) { + let preferredSymbolConfig = UIImage.SymbolConfiguration.configurationPreferringMulticolor() + let preferredSymbol = UIImage(systemName: "circle.hexagongrid.fill") + imageView.image = preferredSymbol + imageView.preferredSymbolConfiguration = preferredSymbolConfig + } + +} diff --git a/UIKitCatalog/TextFieldViewController.swift b/UIKitCatalog/TextFieldViewController.swift index ac0fe58..50638bb 100755 --- a/UIKitCatalog/TextFieldViewController.swift +++ b/UIKitCatalog/TextFieldViewController.swift @@ -7,90 +7,114 @@ A view controller that demonstrates how to use `UITextField`. import UIKit -class TextFieldViewController: UITableViewController { - // MARK: - Properties - - @IBOutlet weak var textField: UITextField! - @IBOutlet weak var tintedTextField: UITextField! - @IBOutlet weak var secureTextField: UITextField! - @IBOutlet weak var specificKeyboardTextField: UITextField! - @IBOutlet weak var customTextField: UITextField! - @IBOutlet weak var searchTextField: CustomTextField! +class TextFieldViewController: BaseTableViewController { - // MARK: View Life Cycle + // Cell identifier for each text field table view cell. + enum TextFieldKind: String, CaseIterable { + case textField + case tintedTextField + case secureTextField + case specificKeyboardTextField + case customTextField + case searchTextField + } override func viewDidLoad() { super.viewDidLoad() - configureTextField() - configureTintedTextField() - configureSecureTextField() - configureSpecificKeyboardTextField() - configureCustomTextField() - configureSearchTextField() + testCells.append(contentsOf: [ + CaseElement(title: NSLocalizedString("DefaultTextFieldTitle", comment: ""), + cellID: TextFieldKind.textField.rawValue, + configHandler: configureTextField), + CaseElement(title: NSLocalizedString("TintedTextFieldTitle", comment: ""), + cellID: TextFieldKind.tintedTextField.rawValue, + configHandler: configureTintedTextField), + CaseElement(title: NSLocalizedString("SecuretTextFieldTitle", comment: ""), + cellID: TextFieldKind.secureTextField.rawValue, + configHandler: configureSecureTextField), + CaseElement(title: NSLocalizedString("SearchTextFieldTitle", comment: ""), + cellID: TextFieldKind.searchTextField.rawValue, + configHandler: configureSearchTextField) + ]) + + if traitCollection.userInterfaceIdiom != .mac { + testCells.append(contentsOf: [ + // Show text field with specific kind of keyboard for iOS only. + CaseElement(title: NSLocalizedString("SpecificKeyboardTextFieldTitle", comment: ""), + cellID: TextFieldKind.specificKeyboardTextField.rawValue, + configHandler: configureSpecificKeyboardTextField), + + // Show text field with custom background for iOS only. + CaseElement(title: NSLocalizedString("CustomTextFieldTitle", comment: ""), + cellID: TextFieldKind.customTextField.rawValue, + configHandler: configureCustomTextField) + ]) + } } - + // MARK: - Configuration - func configureTextField() { + func configureTextField(_ textField: UITextField) { textField.placeholder = NSLocalizedString("Placeholder text", comment: "") textField.autocorrectionType = .yes textField.returnKeyType = .done textField.clearButtonMode = .whileEditing } - func configureTintedTextField() { - tintedTextField.tintColor = UIColor.systemBlue - tintedTextField.textColor = UIColor.systemGreen + func configureTintedTextField(_ textField: UITextField) { + textField.tintColor = UIColor.systemBlue + textField.textColor = UIColor.systemGreen - tintedTextField.placeholder = NSLocalizedString("Placeholder text", comment: "") - tintedTextField.returnKeyType = .done - tintedTextField.clearButtonMode = .never + textField.placeholder = NSLocalizedString("Placeholder text", comment: "") + textField.returnKeyType = .done + textField.clearButtonMode = .never } - func configureSecureTextField() { - secureTextField.isSecureTextEntry = true + func configureSecureTextField(_ textField: UITextField) { + textField.isSecureTextEntry = true - secureTextField.placeholder = NSLocalizedString("Placeholder text", comment: "") - secureTextField.returnKeyType = .done - secureTextField.clearButtonMode = .always + textField.placeholder = NSLocalizedString("Placeholder text", comment: "") + textField.returnKeyType = .done + textField.clearButtonMode = .always } - func configureSearchTextField() { - searchTextField.placeholder = NSLocalizedString("Enter search text", comment: "") - searchTextField.returnKeyType = .done - searchTextField.clearButtonMode = .always - searchTextField.allowsDeletingTokens = true + func configureSearchTextField(_ textField: UITextField) { + if let searchField = textField as? UISearchTextField { + searchField.placeholder = NSLocalizedString("Enter search text", comment: "") + searchField.returnKeyType = .done + searchField.clearButtonMode = .always + searchField.allowsDeletingTokens = true + + // Setup the left view as a symbol image view. + let searchIcon = UIImageView(image: UIImage(systemName: "magnifyingglass")) + searchIcon.tintColor = UIColor.systemGray + searchField.leftView = searchIcon + searchField.leftViewMode = .always - // Setup the left view as a symbol image view. - let searchIcon = UIImageView(image: UIImage(systemName: "magnifyingglass")) - searchIcon.tintColor = UIColor.systemGray - searchTextField.leftView = searchIcon - searchTextField.leftViewMode = .always - - let secondToken = UISearchToken(icon: UIImage(systemName: "staroflife"), text: "Token 2") - searchTextField.insertToken(secondToken, at: 0) - - let firstToken = UISearchToken(icon: UIImage(systemName: "staroflife.fill"), text: "Token 1") - searchTextField.insertToken(firstToken, at: 0) + let secondToken = UISearchToken(icon: UIImage(systemName: "staroflife"), text: "Token 2") + searchField.insertToken(secondToken, at: 0) + + let firstToken = UISearchToken(icon: UIImage(systemName: "staroflife.fill"), text: "Token 1") + searchField.insertToken(firstToken, at: 0) + } } /** There are many different types of keyboards that you may choose to use. The different types of keyboards are defined in the `UITextInputTraits` interface. This example shows how to display a keyboard to help enter email addresses. */ - func configureSpecificKeyboardTextField() { - specificKeyboardTextField.keyboardType = .emailAddress + func configureSpecificKeyboardTextField(_ textField: UITextField) { + textField.keyboardType = .emailAddress - specificKeyboardTextField.placeholder = NSLocalizedString("Placeholder text", comment: "") - specificKeyboardTextField.returnKeyType = .done + textField.placeholder = NSLocalizedString("Placeholder text", comment: "") + textField.returnKeyType = .done } - func configureCustomTextField() { + func configureCustomTextField(_ textField: UITextField) { // Text fields with custom image backgrounds must have no border. - customTextField.borderStyle = .none + textField.borderStyle = .none - customTextField.background = UIImage(named: "text_field_background") + textField.background = UIImage(named: "text_field_background") // Create a purple button to be used as the right view of the custom text field. let purpleImage = UIImage(named: "text_field_purple_right_view")! @@ -99,51 +123,20 @@ class TextFieldViewController: UITableViewController { purpleImageButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 5) purpleImageButton.setImage(purpleImage, for: .normal) purpleImageButton.addTarget(self, action: #selector(TextFieldViewController.customTextFieldPurpleButtonClicked), for: .touchUpInside) - customTextField.rightView = purpleImageButton - customTextField.rightViewMode = .always + textField.rightView = purpleImageButton + textField.rightViewMode = .always - customTextField.placeholder = NSLocalizedString("Placeholder text", comment: "") - customTextField.autocorrectionType = .no - customTextField.clearButtonMode = .never - customTextField.returnKeyType = .done + textField.placeholder = NSLocalizedString("Placeholder text", comment: "") + textField.autocorrectionType = .no + textField.clearButtonMode = .never + textField.returnKeyType = .done } // MARK: - Actions @objc func customTextFieldPurpleButtonClicked() { - print("The custom text field's purple right view button was clicked.") - } - - // MARK: - UITableViewDataSource - - override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - #if targetEnvironment(macCatalyst) - // Don't show text field with custom background for macOS, it does not exist. - if section == 4 { - return "" - } else { - return super.tableView(tableView, titleForHeaderInSection: section) - } - #else - return super.tableView(tableView, titleForHeaderInSection: section) - #endif - } - - // MARK: - UITableViewDelegate - - override func tableView(_ tableView: UITableView, - heightForRowAt indexPath: IndexPath) -> CGFloat { - #if targetEnvironment(macCatalyst) - // Don't show text field with custom background for macOS, it does not exist. - if indexPath.section == 4 { - return 0 - } else { - return super.tableView(tableView, heightForRowAt: indexPath) - } - #else - return super.tableView(tableView, heightForRowAt: indexPath) - #endif + Swift.debugPrint("The custom text field's purple right view button was clicked.") } } @@ -167,18 +160,21 @@ extension TextFieldViewController: UITextFieldDelegate { } // Custom text field for controlling input text placement. -class CustomTextField: UISearchTextField { +class CustomTextField: UITextField { let leftMarginPadding: CGFloat = 12 - + let rightMarginPadding: CGFloat = 36 + override func textRect(forBounds bounds: CGRect) -> CGRect { var rect = bounds rect.origin.x += leftMarginPadding + rect.size.width -= rightMarginPadding return rect } override func editingRect(forBounds bounds: CGRect) -> CGRect { var rect = bounds rect.origin.x += leftMarginPadding + rect.size.width -= rightMarginPadding return rect } diff --git a/UIKitCatalog/TextViewController.swift b/UIKitCatalog/TextViewController.swift index f60d8b1..ce671dd 100755 --- a/UIKitCatalog/TextViewController.swift +++ b/UIKitCatalog/TextViewController.swift @@ -52,7 +52,7 @@ class TextViewController: UIViewController { @objc func handleKeyboardNotification(_ notification: Notification) { - let userInfo = notification.userInfo! + guard let userInfo = notification.userInfo else { return } // Get the animation duration. var animationDuration: TimeInterval = 0 @@ -107,7 +107,7 @@ class TextViewController: UIViewController { entireAttributedText.addAttribute(NSAttributedString.Key.foregroundColor, value: entireTextColor, range: entireRange) textView.attributedText = entireAttributedText - /** Let's modify some of the attributes of the attributed string. + /** Modify some of the attributes of the attributed string. You can modify these attributes yourself to get a better feel for what they do. Note that the initial text is visible in the storyboard. */ @@ -124,9 +124,7 @@ class TextViewController: UIViewController { let underlinedRange = text.range(of: NSLocalizedString("underlined", comment: "")) let tintedRange = text.range(of: NSLocalizedString("tinted", comment: "")) - /** Add bold attribute. Take the current font descriptor and create a new font descriptor - with an additional bold trait. - */ + // Add bold attribute. Take the current font descriptor and create a new font descriptor with an additional bold trait. let boldFontDescriptor = textView.font!.fontDescriptor.withSymbolicTraits(.traitBold) let boldFont = UIFont(descriptor: boldFontDescriptor!, size: 0) attributedText.addAttribute(NSAttributedString.Key.font, value: boldFont, range: boldRange) @@ -156,6 +154,15 @@ class TextViewController: UIViewController { return NSAttributedString(attachment: symbolAttachment) } + func multiColorSymbolAttributedString(name: String) -> NSAttributedString { + let symbolAttachment = NSTextAttachment() + let palleteSymbolConfig = UIImage.SymbolConfiguration(paletteColors: [UIColor.systemOrange, UIColor.systemRed]) + if let symbolImage = UIImage(systemName: name)?.withConfiguration(palleteSymbolConfig) { + symbolAttachment.image = symbolImage + } + return NSAttributedString(attachment: symbolAttachment) + } + func configureTextView() { let bodyFontDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: UIFont.TextStyle.body) let bodyFont = UIFont(descriptor: bodyFontDescriptor, size: 0) @@ -179,6 +186,12 @@ class TextViewController: UIViewController { insertPoint += 1 attributedText.insert(symbolAttributedString(name: "heart.slash"), at: insertPoint) + // Multi-color SF Symbols only in iOS 15 or later. + if #available(iOS 15, *) { + insertPoint += 1 + attributedText.insert(multiColorSymbolAttributedString(name: "arrow.up.heart.fill"), at: insertPoint) + } + // Add the image as an attachment. if let image = UIImage(named: "text_view_attachment") { let textAttachment = NSTextAttachment() diff --git a/UIKitCatalog/UIKitCatalog-Info.plist b/UIKitCatalog/UIKitCatalog-Info.plist index 84a89ed..bb77aaa 100755 --- a/UIKitCatalog/UIKitCatalog-Info.plist +++ b/UIKitCatalog/UIKitCatalog-Info.plist @@ -22,6 +22,29 @@ 1 LSRequiresIPhoneOS + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UILaunchStoryboardName + LaunchScreen + UISceneClassName + UIWindowScene + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -43,28 +66,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UILaunchStoryboardName - LaunchScreen - UISceneClassName - UIWindowScene - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main - - - - diff --git a/UIKitCatalog/UIKitCatalog/Base.lproj/Localizable.stringsdict b/UIKitCatalog/UIKitCatalog/Base.lproj/Localizable.stringsdict new file mode 100644 index 0000000..8dbf8f2 --- /dev/null +++ b/UIKitCatalog/UIKitCatalog/Base.lproj/Localizable.stringsdict @@ -0,0 +1,41 @@ + + + + + Cart Items String + + NSStringLocalizedFormatKey + %#@items@ + items + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %d item + other + %d items + + + + Cart Tooltip String + + NSStringLocalizedFormatKey + %#@items@ + items + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + Cart is Empty + one + Cart has %d item + other + Cart has %d items + + + + diff --git a/UIKitCatalog/VisualEffectViewController.swift b/UIKitCatalog/VisualEffectViewController.swift new file mode 100755 index 0000000..bb4649f --- /dev/null +++ b/UIKitCatalog/VisualEffectViewController.swift @@ -0,0 +1,68 @@ +/* +See LICENSE folder for this sample’s licensing information. + +Abstract: +A view controller that demonstrates how to use `UIVisualEffectView`. +*/ + +import UIKit + +class VisualEffectViewController: UIViewController { + // MARK: - Properties + + @IBOutlet var imageView: UIImageView! + + private var visualEffect: UIVisualEffectView = { + let vev = UIVisualEffectView(effect: UIBlurEffect(style: .regular)) + vev.translatesAutoresizingMaskIntoConstraints = false + return vev + }() + + private var textView: UITextView = { + let textView = UITextView(frame: CGRect()) + textView.font = UIFont.systemFont(ofSize: 14) + textView.text = NSLocalizedString("VisualEffectTextContent", comment: "") + + textView.translatesAutoresizingMaskIntoConstraints = false + textView.backgroundColor = UIColor.clear + if let fontDescriptor = UIFontDescriptor + .preferredFontDescriptor(withTextStyle: UIFont.TextStyle.body) + .withSymbolicTraits(UIFontDescriptor.SymbolicTraits.traitLooseLeading) { + let looseLeadingFont = UIFont(descriptor: fontDescriptor, size: 0) + textView.font = looseLeadingFont + } + return textView + }() + + // MARK: - View Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Add the visual effect view in the same area covering the image view. + view.addSubview(visualEffect) + NSLayoutConstraint.activate([ + visualEffect.topAnchor.constraint(equalTo: imageView.topAnchor), + visualEffect.leadingAnchor.constraint(equalTo: imageView.leadingAnchor), + visualEffect.trailingAnchor.constraint(equalTo: imageView.trailingAnchor), + visualEffect.bottomAnchor.constraint(equalTo: imageView.bottomAnchor) + ]) + + // Add a text view as a subview to the visual effect view. + visualEffect.contentView.addSubview(textView) + NSLayoutConstraint.activate([ + textView.topAnchor.constraint(equalTo: visualEffect.safeAreaLayoutGuide.topAnchor), + textView.leadingAnchor.constraint(equalTo: visualEffect.safeAreaLayoutGuide.leadingAnchor), + textView.trailingAnchor.constraint(equalTo: visualEffect.safeAreaLayoutGuide.trailingAnchor), + textView.bottomAnchor.constraint(equalTo: visualEffect.safeAreaLayoutGuide.bottomAnchor) + ]) + + if #available(iOS 15, *) { + // Use UIToolTipInteraction which is available on iOS 15 or later, add it to the image view. + let toolTipString = NSLocalizedString("VisualEffectToolTipTitle", comment: "") + let interaction = UIToolTipInteraction(defaultToolTip: toolTipString) + imageView.addInteraction(interaction) + } + } + +}