我才刚刚开始学习组合,所以对我还是有点模糊。我想创建一个自定义import tkinter as tk
from tkinter import messagebox
from collections import OrderedDict
class ProjectInfo(tk.Frame):
def __init__(self, parent, controller, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.parent = parent
self.controller = controller
self.widgets_init()
def widgets_init(self):
tk.Label(self,
text = "Rock Controller!",
width = 10,
anchor = "w",
justify = "left").grid(row = 0, column = 0)
tk.Label(self,
text = "Input Name: ").grid(row = 1, column = 0)
self.entry = tk.Entry(self)
self.entry.grid(row = 1, column = 1)
class ConfirmItems(tk.Frame):
def __init__(self, parent, frames, controller, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.parent = parent
self.frames = frames
self.controller = controller
self.widgets_init()
def update_entries(self):
self.controller.project_info.name = self.controller.project_info.entry.get()
def update_frames(self):
self.message = 'Click Cancel go back to reset!\n'
for key, values in self.frames.items():
for v in values:
x = getattr(key, v)
self.message += v + ': ' + str(x) + '\n'
def show_settings(self):
answer = tk.messagebox.askokcancel("Check Settings", self.message)
if answer in ["yes", 1]:
self.quit()
def combine_funcs(self, *funcs):
def combined_func(*args, **kwargs):
for f in funcs:
f(*args, **kwargs)
return combined_func
def widgets_init(self):
self.cancel = tk.Button(self,
text = "Cancel",
command = self.quit)
self.cancel.grid(row = 0, column = 0)
self.submit = tk.Button(self,
text = "Submit",
command = self.combine_funcs(
self.update_entries,
self.update_frames,
self.show_settings))
# command = lambda:[self.update_frames(), self.show_settings()]
self.submit.grid(row = 0, column = 1)
class MainWindow(tk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.parent = parent
self.controller = self
self.project_info = ProjectInfo(self.parent, self.controller)
self.project_info.grid(row = 0)
self.widgets_init()
self.confirm_items = ConfirmItems(self.parent, self.frames, self.controller)
self.confirm_items.grid(row = 1)
def widgets_init(self):
self.dict_list = [(self.project_info, ('name',))]
self.frames = OrderedDict(self.dict_list)
def main():
root = tk.Tk()
root.title("Welcome to Controller World!")
root.geometry("300x300")
gui = MainWindow(root)
root.mainloop()
if __name__ == "__main__":
main()
,它将使用Publisher
来显示当前用户位置。我希望它以这种方式工作:CLLocationManager
仅在连接了一些订户时才开始更新位置。并且在所有订户被删除,取消等之后,它应该停止更新位置。这可能吗?如何创建这样的locationManager
?这也是正确的方法,还是存在问题?
答案 0 :(得分:2)
我是Combine的新手,但是今天刚创建的my attempt是正在进行中的工作,因此可能没有正确完成。这个想法是使用CLLocationManager
的设计方式,即在需要时使用多个实例。
// Requirements: a NSLocationWhenInUseUsageDescription entry in Info.plist and call requestWhenInUseAuthorization
import Foundation
import Combine
import CoreLocation
extension CLLocationManager {
public static func publishLocation() -> LocationPublisher{
return .init()
}
public struct LocationPublisher: Publisher {
public typealias Output = CLLocation
public typealias Failure = Never
public func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
let subscription = LocationSubscription(subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
final class LocationSubscription<S: Subscriber> : NSObject, CLLocationManagerDelegate, Subscription where S.Input == Output, S.Failure == Failure{
var subscriber: S
var locationManager = CLLocationManager()
init(subscriber: S) {
self.subscriber = subscriber
super.init()
locationManager.delegate = self
}
func request(_ demand: Subscribers.Demand) {
locationManager.startUpdatingLocation()
locationManager.requestWhenInUseAuthorization()
}
func cancel() {
locationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
for location in locations {
_ = subscriber.receive(location)
}
}
}
}
}
测试器
import SwiftUI
import CoreData
import CoreLocation
import Combine
class Locator : ObservableObject {
@Published var location = CLLocation()
var cancellable : AnyCancellable?
init() {
}
func start(){
cancellable = CLLocationManager.publishLocation()
.assign(to: \.location, on: self)
}
}
struct ContentView: View {
@StateObject var locator = Locator()
var body: some View {
VStack {
Text("Location \(locator.location)")
}
.onAppear(){
locator.start()
}
}
}
接下来,我计划添加publishAuthorization
并建立一个管道。我还想为init提供配置参数,这样,如果有多个订户,他们可以以相同的方式配置位置管理器,例如distanceFilter
。
答案 1 :(得分:0)
所需内容的基础非常简单。 Using Combine中有一个示例,该示例将CoreLocation与充当代理的对象包装在一起,返回了CLHeading
更新的发布者。
CoreLocation本身不会自动启动或停止这种事情,在代理对象中,我复制了该模式以允许您手动启动和停止更新过程。
代码的核心位于https://github.com/heckj/swiftui-notes/blob/master/UIKit-Combine/LocationHeadingProxy.swift
import Foundation
import Combine
import CoreLocation
final class LocationHeadingProxy: NSObject, CLLocationManagerDelegate {
let mgr: CLLocationManager
private let headingPublisher: PassthroughSubject<CLHeading, Error>
var publisher: AnyPublisher<CLHeading, Error>
override init() {
mgr = CLLocationManager()
headingPublisher = PassthroughSubject<CLHeading, Error>()
publisher = headingPublisher.eraseToAnyPublisher()
super.init()
mgr.delegate = self
}
func enable() {
mgr.startUpdatingHeading()
}
func disable() {
mgr.stopUpdatingHeading()
}
// MARK - delegate methods
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
headingPublisher.send(newHeading)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
headingPublisher.send(completion: Subscribers.Completion.failure(error))
}
}
这不能完全满足您的要求,因为它不会在订阅后自动启动更新,但是我怀疑您可以扩展此功能以启用该功能。
到目前为止,我还没有通过实现协议所需的所有方法来研究自己的发布者,因此我没有扩展到该机制的细节。当您希望对更新进行明确控制时,尽管大多数发布者和运营商会在创建发布者或订阅时触发,但是Combine本身具有ConnectablePublisher的概念。
通常, IF 应该最好用您的用例来回答。在某些情况下,您需要先创建管道并进行订阅,然后再更新视图-如果是这种情况,那么推迟要求后台更新将为您节省一些处理能力和能耗。
在使用此CoreLocation发布者的UIKit示例中,我还具有适当的机制来验证请求的权限以允许位置更新,该机制嵌入在示例视图控制器中:https://github.com/heckj/swiftui-notes/blob/master/UIKit-Combine/HeadingViewController.swift