完善任务流程、键盘适配与页面交互

This commit is contained in:
2026-07-10 15:56:15 +08:00
parent f88a85a807
commit ab5220e460
189 changed files with 16779 additions and 1038 deletions

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,91 @@
//
// IQTextInputViewInfo.swift
// https://github.com/hackiftekhar/IQTextInputViewNotification
// Copyright (c) 2013-24 Iftekhar Qurashi.
//
// 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:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import UIKit
public import IQKeyboardCore
@available(iOSApplicationExtension, unavailable)
@MainActor
public struct IQTextInputViewInfo: Equatable {
nonisolated public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.textInputView == rhs.textInputView &&
lhs.event == rhs.event
}
@MainActor
@objc public enum Event: Int {
case beginEditing
case endEditing
public var name: String {
switch self {
case .beginEditing:
return "BeginEditing"
case .endEditing:
return "EndEditing"
}
}
}
public let event: Event
public let textInputView: any IQTextInputView
internal init?(notification: Notification, event: Event) {
guard let view: any IQTextInputView = notification.object as? (any IQTextInputView) else {
return nil
}
self.event = event
textInputView = view
}
}
// MARK: Deprecated
@available(iOSApplicationExtension, unavailable)
public extension IQTextInputViewInfo {
@available(*, unavailable, renamed: "event")
var name: Event { event }
@available(*, unavailable, renamed: "textInputView")
var textFieldView: any IQTextInputView { textInputView }
}
@available(*, unavailable, renamed: "IQTextInputViewInfo")
@MainActor
public struct IQTextFieldViewInfo: Equatable {}
@available(iOSApplicationExtension, unavailable)
@objcMembers public class IQTextInputViewInfoObjC: NSObject {
private let wrappedValue: IQTextInputViewInfo
public var event: IQTextInputViewInfo.Event { wrappedValue.event }
public var textInputView: any IQTextInputView { wrappedValue.textInputView }
init(wrappedValue: IQTextInputViewInfo){
self.wrappedValue = wrappedValue
}
}

View File

@ -0,0 +1,196 @@
//
// IQTextInputViewNotification.swift
// https://github.com/hackiftekhar/IQTextInputViewNotification
// Copyright (c) 2013-24 Iftekhar Qurashi.
//
// 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:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import UIKit
import Combine
public import IQKeyboardCore
@available(iOSApplicationExtension, unavailable)
@MainActor
@objcMembers public final class IQTextInputViewNotification: NSObject {
private var storage: Set<AnyCancellable> = []
private var textInputViewObservers: [AnyHashable: TextInputViewCompletion] = [:]
private var findInteractionTextInputViewInfo: IQTextInputViewInfo?
public private(set) var textInputViewInfo: IQTextInputViewInfo?
public var event: IQTextInputViewInfo.Event? {
textInputViewInfo?.event
}
public var textInputView: (any IQTextInputView)? {
return textInputViewInfo?.textInputView
}
public override init() {
super.init()
// Registering for TextInputView notification.
do {
let beginEditingNotificationNames: [Notification.Name] = [
UITextField.textDidBeginEditingNotification,
UITextView.textDidBeginEditingNotification
]
for notificationName in beginEditingNotificationNames {
NotificationCenter.default.publisher(for: notificationName)
.compactMap({ IQTextInputViewInfo(notification: $0, event: .beginEditing) })
.sink(receiveValue: { [weak self] info in
guard let self = self else { return }
self.didBeginEditing(info: info)
})
.store(in: &storage)
}
}
do {
let endEditingNotificationNames: [Notification.Name] = [
UITextField.textDidEndEditingNotification,
UITextView.textDidEndEditingNotification
]
for notificationName in endEditingNotificationNames {
NotificationCenter.default.publisher(for: notificationName)
.compactMap({ IQTextInputViewInfo(notification: $0, event: .endEditing) })
.sink(receiveValue: { [weak self] info in
guard let self = self else { return }
self.didEndEditing(info: info)
})
.store(in: &storage)
}
}
}
private func didBeginEditing(info: IQTextInputViewInfo) {
#if compiler(>=5.7) // Xcode 14
if #available(iOS 16.0, *),
let findInteractionTextInputViewInfo = findInteractionTextInputViewInfo,
findInteractionTextInputViewInfo.textInputView.iqFindInteraction?.isFindNavigatorVisible == true {
// // This means the this didBeginEditing call comes due to find interaction
textInputViewInfo = findInteractionTextInputViewInfo
sendEvent(info: findInteractionTextInputViewInfo)
} else if textInputViewInfo != info {
textInputViewInfo = info
findInteractionTextInputViewInfo = nil
sendEvent(info: info)
} else {
findInteractionTextInputViewInfo = nil
}
#else
if textInputViewInfo != info {
textInputViewInfo = info
findInteractionTextInputViewInfo = nil
sendEvent(info: info)
} else {
findInteractionTextInputViewInfo = nil
}
#endif
}
private func didEndEditing(info: IQTextInputViewInfo) {
if textInputViewInfo != info {
#if compiler(>=5.7) // Xcode 14
if #available(iOS 16.0, *),
info.textInputView.iqIsFindInteractionEnabled {
findInteractionTextInputViewInfo = textInputViewInfo
} else {
findInteractionTextInputViewInfo = nil
}
#else
findInteractionTextInputViewInfo = nil
#endif
textInputViewInfo = info
sendEvent(info: info)
textInputViewInfo = nil
}
}
}
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public extension IQTextInputViewNotification {
typealias TextInputViewCompletion = (_ event: IQTextInputViewInfo.Event,
_ textInputView: any IQTextInputView) -> Void
func subscribe(identifier: AnyHashable, changeHandler: @escaping TextInputViewCompletion) {
textInputViewObservers[identifier] = changeHandler
if let textInputViewInfo = textInputViewInfo {
changeHandler(textInputViewInfo.event, textInputViewInfo.textInputView)
}
}
func unsubscribe(identifier: AnyHashable) {
textInputViewObservers[identifier] = nil
}
func isSubscribed(identifier: AnyHashable) -> Bool {
return textInputViewObservers[identifier] != nil
}
@nonobjc
private func sendEvent(info: IQTextInputViewInfo) {
for block in textInputViewObservers.values {
block(info.event, info.textInputView)
}
}
}
// MARK: Deprecated
@available(iOSApplicationExtension, unavailable)
@MainActor
public extension IQTextInputViewNotification {
@available(*, unavailable, renamed: "textInputViewInfo")
var textFieldViewInfo: IQTextInputViewInfo? { textInputViewInfo }
@available(*, unavailable, renamed: "textInputView")
var textFieldView: (some IQTextInputView)? { textInputView }
@available(*, unavailable, renamed: "subscribe(identifier:changeHandler:)")
func registerTextFieldViewChange(identifier: AnyHashable, changeHandler: @escaping TextInputViewCompletion) {}
@available(*, unavailable, renamed: "unsubscribe(identifier:)")
func unregisterSizeChange(identifier: AnyHashable) {}
}
@available(*, unavailable, renamed: "IQTextInputViewNotification")
@MainActor
@objcMembers public final class IQTextFieldViewListener: NSObject {}
@available(iOSApplicationExtension, unavailable)
@MainActor
@objc public extension IQTextInputViewNotification {
var textInputViewInfoObjc: IQTextInputViewInfoObjC? {
guard let textInputViewInfo = textInputViewInfo else { return nil }
return IQTextInputViewInfoObjC(wrappedValue: textInputViewInfo)
}
}

19
Pods/IQTextInputViewNotification/LICENSE generated Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2024 hackiftekhar <ideviftekhar@gmail.com>
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:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,66 @@
# IQTextInputViewNotification
[![CI Status](https://img.shields.io/travis/hackiftekhar/IQTextInputViewNotification.svg?style=flat)](https://travis-ci.org/hackiftekhar/IQTextInputViewNotification)
[![Version](https://img.shields.io/cocoapods/v/IQTextInputViewNotification.svg?style=flat)](https://cocoapods.org/pods/IQTextInputViewNotification)
[![License](https://img.shields.io/cocoapods/l/IQTextInputViewNotification.svg?style=flat)](https://cocoapods.org/pods/IQTextInputViewNotification)
[![Platform](https://img.shields.io/cocoapods/p/IQTextInputViewNotification.svg?style=flat)](https://cocoapods.org/pods/IQTextInputViewNotification)
![Screenshot](https://raw.githubusercontent.com/hackiftekhar/IQTextInputViewNotification/master/Screenshot/IQTextInputViewNotificationScreenshot.png)
## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
## Installation
IQTextInputViewNotification is available through [CocoaPods](https://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod 'IQTextInputViewNotification'
```
## Usage
To observe textInputView becomeFirstResponder and resignFirstResponder changes, subscribe to the textInputView events:-
```swift
import IQTextInputViewNotification
class ViewController: UIViewController {
private let textInputViewObserver: IQTextInputViewNotification = .init()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Subscribe
textInputViewObserver.subscribe(identifier: "YOUR_UNIQUE_IDENTIFIER") {info in
print(info.event.name) // BeginEditing or EndEditing event
print(info.textInputView) // TextInputView which begin editing or end editing
// Write your own logic here based on event
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Unsubscribe
textInputViewObserver.unsubscribe(identifier: "YOUR_UNIQUE_IDENTIFIER")
}
}
```
## Author
Iftekhar Qurashi hack.iftekhar@gmail.com
## Flow
![Screenshot](https://raw.githubusercontent.com/hackiftekhar/IQTextInputViewNotification/master/Screenshot/FlowDiagram.jpg)
## License
IQTextInputViewNotification is available under the MIT license. See the LICENSE file for more info.