widgetPicker view compatibility fixes for low iOS versions
This commit is contained in:
1 parent
b258c58d59
commit
c3cb31d443
2 files changed
+134
-26
No files matched your search
@@ -225,6 +225,79 @@ struct FunctionalButtonOption: Identifiable {
|
|||||||
let forcedComboMode: WidgetCreateComboMode?
|
let forcedComboMode: WidgetCreateComboMode?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(iOS 13.0, *)
|
||||||
|
private final class WidgetLabelTextField: UITextField {
|
||||||
|
private let horizontalInset: CGFloat = 12
|
||||||
|
|
||||||
|
override func textRect(forBounds bounds: CGRect) -> CGRect {
|
||||||
|
bounds.insetBy(dx: horizontalInset, dy: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func editingRect(forBounds bounds: CGRect) -> CGRect {
|
||||||
|
bounds.insetBy(dx: horizontalInset, dy: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
|
||||||
|
bounds.insetBy(dx: horizontalInset, dy: 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 13.0, *)
|
||||||
|
private struct InteractiveTextField: UIViewRepresentable {
|
||||||
|
final class Coordinator: NSObject, UITextFieldDelegate {
|
||||||
|
var parent: InteractiveTextField
|
||||||
|
|
||||||
|
init(parent: InteractiveTextField) {
|
||||||
|
self.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldDidChangeSelection(_ textField: UITextField) {
|
||||||
|
parent.text = textField.text ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
|
textField.resignFirstResponder()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let placeholder: String
|
||||||
|
@Binding var text: String
|
||||||
|
|
||||||
|
func makeCoordinator() -> Coordinator {
|
||||||
|
Coordinator(parent: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> UITextField {
|
||||||
|
let textField = WidgetLabelTextField(frame: .zero)
|
||||||
|
textField.borderStyle = .none
|
||||||
|
textField.delegate = context.coordinator
|
||||||
|
textField.backgroundColor = UIColor.white.withAlphaComponent(0.78)
|
||||||
|
textField.textColor = UIColor.black.withAlphaComponent(0.82)
|
||||||
|
textField.attributedPlaceholder = NSAttributedString(
|
||||||
|
string: placeholder,
|
||||||
|
attributes: [.foregroundColor: UIColor.systemGray3]
|
||||||
|
)
|
||||||
|
textField.returnKeyType = .done
|
||||||
|
textField.clearButtonMode = .whileEditing
|
||||||
|
textField.layer.cornerRadius = 12
|
||||||
|
textField.layer.masksToBounds = true
|
||||||
|
textField.layer.borderWidth = 1
|
||||||
|
textField.layer.borderColor = UIColor.white.withAlphaComponent(0.88).cgColor
|
||||||
|
return textField
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UITextField, context: Context) {
|
||||||
|
context.coordinator.parent = self
|
||||||
|
if uiView.text != text {
|
||||||
|
uiView.text = text
|
||||||
|
}
|
||||||
|
if uiView.placeholder != placeholder {
|
||||||
|
uiView.placeholder = placeholder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@available(iOS 13.0, *)
|
@available(iOS 13.0, *)
|
||||||
private struct WidgetPickerMetrics {
|
private struct WidgetPickerMetrics {
|
||||||
let isPhone: Bool
|
let isPhone: Bool
|
||||||
@@ -736,6 +809,7 @@ struct WidgetPickerView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.allowsHitTesting(!showGyroPicker && !showCreateWidgetSheet)
|
||||||
.padding(.top, verticalOuterPadding)
|
.padding(.top, verticalOuterPadding)
|
||||||
.padding(.leading, metrics.outerPadding)
|
.padding(.leading, metrics.outerPadding)
|
||||||
.padding(.trailing, metrics.outerPadding)
|
.padding(.trailing, metrics.outerPadding)
|
||||||
@@ -1573,6 +1647,7 @@ struct WidgetPickerView: View {
|
|||||||
let contentSpacing: CGFloat = isPhone ? 10 : 16
|
let contentSpacing: CGFloat = isPhone ? 10 : 16
|
||||||
let sectionSpacing: CGFloat = isPhone ? 7 : 10
|
let sectionSpacing: CGFloat = isPhone ? 7 : 10
|
||||||
let fieldHeight: CGFloat = isPhone ? 30 : 38
|
let fieldHeight: CGFloat = isPhone ? 30 : 38
|
||||||
|
let segmentedControlHeight: CGFloat = isPhone ? 36 : 38
|
||||||
let actionSpacing: CGFloat = isPhone ? 8 : 12
|
let actionSpacing: CGFloat = isPhone ? 8 : 12
|
||||||
let actionHeight: CGFloat = isPhone ? 34 : 44
|
let actionHeight: CGFloat = isPhone ? 34 : 44
|
||||||
let actionFontSize: CGFloat = isPhone ? 13 : 15
|
let actionFontSize: CGFloat = isPhone ? 13 : 15
|
||||||
@@ -1583,37 +1658,26 @@ struct WidgetPickerView: View {
|
|||||||
return ZStack {
|
return ZStack {
|
||||||
Color.black.opacity(0.28)
|
Color.black.opacity(0.28)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
.contentShape(Rectangle())
|
|
||||||
.onTapGesture {
|
|
||||||
setCreateWidgetSheetVisible(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: contentSpacing) {
|
VStack(alignment: .leading, spacing: contentSpacing) {
|
||||||
VStack(alignment: .leading, spacing: sectionSpacing) {
|
VStack(alignment: .leading, spacing: sectionSpacing) {
|
||||||
if targetWidgetKind == .button {
|
if targetWidgetKind == .button {
|
||||||
if showsButtonLabelField {
|
if showsButtonLabelField {
|
||||||
createWidgetSection(title: SwiftLocalizationHelper.localizedString(forKey: "Button label")) {
|
createWidgetSection(title: SwiftLocalizationHelper.localizedString(forKey: "Button label")) {
|
||||||
TextField(
|
InteractiveTextField(
|
||||||
SwiftLocalizationHelper.localizedString(forKey: "Enter label(optional)"),
|
placeholder: SwiftLocalizationHelper.localizedString(forKey: "Enter label(optional)"),
|
||||||
text: $widgetButtonLabel
|
text: $widgetButtonLabel
|
||||||
)
|
)
|
||||||
.foregroundColor(Color.black.opacity(0.82))
|
|
||||||
.environment(\.colorScheme, .light)
|
|
||||||
.padding(.horizontal, 12)
|
|
||||||
.frame(height: fieldHeight)
|
.frame(height: fieldHeight)
|
||||||
.background(
|
|
||||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
||||||
.fill(Color.white.opacity(0.78))
|
|
||||||
)
|
|
||||||
.overlay(
|
|
||||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
||||||
.stroke(Color.white.opacity(0.88), lineWidth: 1)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
.zIndex(40)
|
||||||
}
|
}
|
||||||
|
|
||||||
if showsComboModeControl {
|
if showsComboModeControl {
|
||||||
createWidgetSection(title: SwiftLocalizationHelper.localizedString(forKey: "Combination mode")) {
|
createInteractiveControlSection(
|
||||||
|
title: SwiftLocalizationHelper.localizedString(forKey: "Combination mode"),
|
||||||
|
controlHeight: segmentedControlHeight
|
||||||
|
) {
|
||||||
Picker("", selection: widgetComboModeBinding) {
|
Picker("", selection: widgetComboModeBinding) {
|
||||||
ForEach(WidgetCreateComboMode.allCases) { mode in
|
ForEach(WidgetCreateComboMode.allCases) { mode in
|
||||||
Text(mode.title).tag(mode)
|
Text(mode.title).tag(mode)
|
||||||
@@ -1621,13 +1685,18 @@ struct WidgetPickerView: View {
|
|||||||
}
|
}
|
||||||
.pickerStyle(SegmentedPickerStyle())
|
.pickerStyle(SegmentedPickerStyle())
|
||||||
.environment(\.colorScheme, .light)
|
.environment(\.colorScheme, .light)
|
||||||
|
.frame(height: segmentedControlHeight)
|
||||||
.disabled(isComboModeLocked)
|
.disabled(isComboModeLocked)
|
||||||
.opacity(isComboModeLocked ? 0.45 : 1.0)
|
.opacity(isComboModeLocked ? 0.45 : 1.0)
|
||||||
}
|
}
|
||||||
|
.zIndex(30)
|
||||||
}
|
}
|
||||||
|
|
||||||
if showsShortcutButtonModeControl {
|
if showsShortcutButtonModeControl {
|
||||||
createWidgetSection(title: SwiftLocalizationHelper.localizedString(forKey: "Button mode")) {
|
createInteractiveControlSection(
|
||||||
|
title: SwiftLocalizationHelper.localizedString(forKey: "Button mode"),
|
||||||
|
controlHeight: segmentedControlHeight
|
||||||
|
) {
|
||||||
Picker("", selection: $shortcutPickerButtonMode) {
|
Picker("", selection: $shortcutPickerButtonMode) {
|
||||||
ForEach(ShortcutPickerButtonMode.allCases) { mode in
|
ForEach(ShortcutPickerButtonMode.allCases) { mode in
|
||||||
Text(mode.title).tag(mode)
|
Text(mode.title).tag(mode)
|
||||||
@@ -1635,25 +1704,34 @@ struct WidgetPickerView: View {
|
|||||||
}
|
}
|
||||||
.pickerStyle(SegmentedPickerStyle())
|
.pickerStyle(SegmentedPickerStyle())
|
||||||
.environment(\.colorScheme, .light)
|
.environment(\.colorScheme, .light)
|
||||||
|
.frame(height: segmentedControlHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if showsIntervalSlider {
|
if showsIntervalSlider {
|
||||||
createWidgetSection(title: "\(SwiftLocalizationHelper.localizedString(forKey: "Trigger interval")): \(effectiveTriggerIntervalValue) ms") {
|
createInteractiveControlSection(
|
||||||
|
title: "\(SwiftLocalizationHelper.localizedString(forKey: "Trigger interval")): \(effectiveTriggerIntervalValue) ms",
|
||||||
|
controlHeight: fieldHeight
|
||||||
|
) {
|
||||||
Slider(
|
Slider(
|
||||||
value: $widgetTriggerInterval,
|
value: $widgetTriggerInterval,
|
||||||
in: 0...2000,
|
in: 0...2000,
|
||||||
step: 1
|
step: 1
|
||||||
)
|
)
|
||||||
.environment(\.colorScheme, .light)
|
.environment(\.colorScheme, .light)
|
||||||
.disabled(isShortcutMode)
|
.frame(height: fieldHeight)
|
||||||
.opacity(isShortcutMode ? 0.45 : 1.0)
|
.disabled(isShortcutMode)
|
||||||
|
.opacity(isShortcutMode ? 0.45 : 1.0)
|
||||||
}
|
}
|
||||||
|
.zIndex(20)
|
||||||
}
|
}
|
||||||
|
|
||||||
if targetWidgetKind == .button, showsShapeControl {
|
if targetWidgetKind == .button, showsShapeControl {
|
||||||
createWidgetSection(title: SwiftLocalizationHelper.localizedString(forKey: "Shape")) {
|
createInteractiveControlSection(
|
||||||
|
title: SwiftLocalizationHelper.localizedString(forKey: "Shape"),
|
||||||
|
controlHeight: segmentedControlHeight
|
||||||
|
) {
|
||||||
Picker("", selection: $widgetShape) {
|
Picker("", selection: $widgetShape) {
|
||||||
ForEach(WidgetCreateShape.allCases) { shape in
|
ForEach(WidgetCreateShape.allCases) { shape in
|
||||||
Text(shape.title).tag(shape)
|
Text(shape.title).tag(shape)
|
||||||
@@ -1661,7 +1739,9 @@ struct WidgetPickerView: View {
|
|||||||
}
|
}
|
||||||
.pickerStyle(SegmentedPickerStyle())
|
.pickerStyle(SegmentedPickerStyle())
|
||||||
.environment(\.colorScheme, .light)
|
.environment(\.colorScheme, .light)
|
||||||
|
.frame(height: segmentedControlHeight)
|
||||||
}
|
}
|
||||||
|
.zIndex(10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1687,6 +1767,7 @@ struct WidgetPickerView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, isPhone ? 2 : 0)
|
.padding(.top, isPhone ? 2 : 0)
|
||||||
|
.zIndex(0)
|
||||||
}
|
}
|
||||||
.padding(cardPadding)
|
.padding(cardPadding)
|
||||||
.frame(maxWidth: cardMaxWidth)
|
.frame(maxWidth: cardMaxWidth)
|
||||||
@@ -1703,7 +1784,22 @@ struct WidgetPickerView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createWidgetSection<Content: View>(title: String, @ViewBuilder content: () -> Content) -> some View {
|
private func createInteractiveControlSection<Content: View>(
|
||||||
|
title: String,
|
||||||
|
controlHeight: CGFloat,
|
||||||
|
@ViewBuilder content: () -> Content
|
||||||
|
) -> some View {
|
||||||
|
createWidgetSection(title: title, clipsContentToBounds: true) {
|
||||||
|
content()
|
||||||
|
.frame(height: controlHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createWidgetSection<Content: View>(
|
||||||
|
title: String,
|
||||||
|
clipsContentToBounds: Bool = false,
|
||||||
|
@ViewBuilder content: () -> Content
|
||||||
|
) -> some View {
|
||||||
let isPhone = UIDevice.current.userInterfaceIdiom == .phone
|
let isPhone = UIDevice.current.userInterfaceIdiom == .phone
|
||||||
let titleFontSize: CGFloat = isPhone ? 11.5 : 13
|
let titleFontSize: CGFloat = isPhone ? 11.5 : 13
|
||||||
let sectionSpacing: CGFloat = isPhone ? 5 : 7
|
let sectionSpacing: CGFloat = isPhone ? 5 : 7
|
||||||
@@ -1716,7 +1812,7 @@ struct WidgetPickerView: View {
|
|||||||
.font(.system(size: titleFontSize, weight: .bold, design: .rounded))
|
.font(.system(size: titleFontSize, weight: .bold, design: .rounded))
|
||||||
.foregroundColor(Color.black.opacity(0.64))
|
.foregroundColor(Color.black.opacity(0.64))
|
||||||
|
|
||||||
content()
|
let sectionContent = content()
|
||||||
.padding(.horizontal, sectionHorizontalPadding)
|
.padding(.horizontal, sectionHorizontalPadding)
|
||||||
.padding(.vertical, sectionVerticalPadding)
|
.padding(.vertical, sectionVerticalPadding)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
@@ -1728,6 +1824,18 @@ struct WidgetPickerView: View {
|
|||||||
RoundedRectangle(cornerRadius: sectionCornerRadius, style: .continuous)
|
RoundedRectangle(cornerRadius: sectionCornerRadius, style: .continuous)
|
||||||
.stroke(Color.white.opacity(0.94), lineWidth: 1)
|
.stroke(Color.white.opacity(0.94), lineWidth: 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if clipsContentToBounds {
|
||||||
|
sectionContent
|
||||||
|
.clipShape(
|
||||||
|
RoundedRectangle(cornerRadius: sectionCornerRadius, style: .continuous)
|
||||||
|
)
|
||||||
|
.contentShape(
|
||||||
|
RoundedRectangle(cornerRadius: sectionCornerRadius, style: .continuous)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
sectionContent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ public final class WidgetPickerViewController: UIViewController {
|
|||||||
overlayWindow.windowLevel = .alert + 1
|
overlayWindow.windowLevel = .alert + 1
|
||||||
overlayWindow.backgroundColor = .clear
|
overlayWindow.backgroundColor = .clear
|
||||||
overlayWindow.rootViewController = overlayHostViewController
|
overlayWindow.rootViewController = overlayHostViewController
|
||||||
overlayWindow.isHidden = false
|
overlayWindow.makeKeyAndVisible()
|
||||||
Self.overlayWindow = overlayWindow
|
Self.overlayWindow = overlayWindow
|
||||||
|
|
||||||
loadViewIfNeeded()
|
loadViewIfNeeded()
|
||||||
|
|||||||
Reference in new issue
Block a user