Swift: pass ARView to Coordinator - swift

really stuck on this.
I'm trying to pass ARView from MakeUIView to makeCoordinator
I really need this to use ARView inside of #objc func handleTap
struct ARViewContainer: UIViewRepresentable{
func makeUIView(context: Context) -> ARView {
let myARView = ARView(frame: .zero)
//...config and things….
let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handleTap(_:)))
myARView.addGestureRecognizer(tapGesture)
return myARView
}
func makeCoordinator() -> Coordinator {
Coordinator("whatshouldiusehere", self.$focusObject, self.$focusName)
}
class Coordinator: NSObject {
private let view: ARView
private var object: Binding<Entity?>
private var objectname: Binding<String?>
init(_ view: ARView, _ obj: Binding<Entity?>, _ objname: Binding<String?>) {
self.objectname = objname
self.object = obj
self.view = view
super.init()
}
#objc func handleTap(_ sender: UIGestureRecognizer? = nil) {
guard let touchInView = sender?.location(in: view) else {
return
}
guard let hitEntity = view.entity(at: touchInView) else {return}
//doing something with object here, assigning to #Binding for example
}
}
}
I can't move myARView = ARView(frame: .zero) outside of makeUIView, cuz I'm using SwiftUI and it inits every time when variables changes.
But how I can pass it in any way?
Or any other option to access Binding with ARView same time.

A coordinator is available via context, so you can inject it via property, like
struct ARViewContainer: UIViewRepresentable{
func makeUIView(context: Context) -> ARView {
let myARView = ARView(frame: .zero)
//...config and things….
let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.handleTap(_:)))
myARView.addGestureRecognizer(tapGesture)
context.coordinator.view = myARView // << inject here !!
return myARView
}
func makeCoordinator() -> Coordinator {
Coordinator(self.$focusObject, self.$focusName)
}
class Coordinator: NSObject {
var view: ARView? // << optional initially
private var object: Binding<Entity?>
private var objectname: Binding<String?>
init(_ obj: Binding<Entity?>, _ objname: Binding<String?>) {
self.objectname = objname
self.object = obj
super.init()
}
// ... other code update accordingly
}

Related

How to open a SwiftUI view by tapping an entity in RealityKit?

I am using SwiftUI with RealityKit. As displayed in the code below, I have a plane entity that when tapped simply prints the name of the entity. What approach should I take toward navigating to a new view when I tap the entity? It would be preferable to navigate as with a navigation link in a normal view, but if that is not possible then perhaps a fullScreenCover?
ARViewContainer.swift:
class Coordinator: NSObject {
weak var view: ARView?
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
guard let view = self.view else { return }
let tapLocation = recognizer.location(in: view)
if let entity = view.entity(at: tapLocation) as? ModelEntity {
print(entity.name)
}
}
}
struct ARViewContainer: UIViewRepresentable {
typealias UIViewType = ARView
func makeUIView(context: Context) -> ARView{
let arView = ARView(frame: .zero, cameraMode: .ar, automaticallyConfigureSession: true)
context.coordinator.view = arView
arView.addGestureRecognizer(UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap)))
arView.scene.anchors.removeAll()
let anchor = AnchorEntity()
let plane = MeshResource.generatePlane(width: 1, height: 1)
var material = UnlitMaterial()
material.color = .init(tint: .white,
texture: .init(try! .load(named: "instagram")))
let planeEntity = ModelEntity(mesh: plane, materials: [material])
planeEntity.generateCollisionShapes(recursive: true)
planeEntity.name = "Plane Entity"
planeEntity.position.z -= 1.0
planeEntity.setParent(anchor)
arView.scene.addAnchor(anchor)
return arView
}
func updateUIView(_ uiView: ARView, context: Context){
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
}
ContentView.swift
struct ContentView: View {
#State var open = false
var body: some View {
NavigationView{
ZStack {
ARViewContainer()
.ignoresSafeArea(.all)
}
}
}
}
View I want to navigate to:
struct TestView : View {
var body : some View {
VStack{
Text("Test View")
}
}
}
Manage the state of the view in an observable object and modify it from your AR view.
struct ContentView: View {
#ObservedObject var settings = Settings.shared
var body: some View {
NavigationView {
ZStack {
ARViewContainer()
.ignoresSafeArea(.all)
NavigationLink("", isActive: $settings.shouldOpenDetailsView) {
TestView()
}
}
}
}
}
class Settings: ObservableObject {
static let shared = Settings()
#Published var shouldOpenDetailsView = false
}
class Coordinator: NSObject {
weak var view: ARView?
#objc func handleTap(_ recognizer: UITapGestureRecognizer) {
guard let view = self.view else { return }
let tapLocation = recognizer.location(in: view)
if let entity = view.entity(at: tapLocation) as? ModelEntity {
Settings.shared.shouldOpenDetailsView = true
}
}
}

How can i access ViewController Class from Coordinator?

I am using a UIViewControllerRepresentable Class in my SwiftUI like this:
struct CalendarDayView: UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeUIViewController(context: Context) -> DayViewController {
let dayView = DayViewController()
dayView.delegate = context.coordinator
return dayView
}
func updateUIViewController(_ dayView: DayViewController, context: Context) {
dayView.delegate = context.coordinator
}
typealias UIViewControllerType = DayViewController
class Coordinator: NSObject, DayViewDelegate {
var parent: CalendarDayView
init(_ parent: CalendarDayView) {
self.parent = parent
}
func dayViewDidSelectEventView(_ eventView: EventView) {
guard let ckEvent = eventView.descriptor as? EKWrapper else { return }
let ekEvent = ckEvent.ekEvent
endEventEditing() // Cannot find 'endEventEditing' in scope
}
}
}
In the dayViewDidSelectEventView() function i need access to the endEventEditing() function, coming from the DayViewController Class. I already try to pass the parent to the Coordinator, but that doesn't helpful. I can't access the endEventEditing() func over the parent variable like this:
parent.endEventEditing() // Value of type 'CalendarDayView' has no member 'endEventEditing'
So is there a way to access the DayViewController Class, generated by the makeUIViewController func inside Coordinator?
Can i use/pass the dayView variable
let dayView = DayViewController()
to my Coordinator so i can use functions coming from this Controller?
You can inject it in coordinator's property (having it weak to avoid cross-references).
Here is a demo:
struct CalendarDayView: UIViewControllerRepresentable {
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
func makeUIViewController(context: Context) -> DayViewController {
let dayView = DayViewController()
dayView.delegate = context.coordinator
context.coordinator.viewController = dayView // << inject !!
return dayView
}
func updateUIViewController(_ dayView: DayViewController, context: Context) {
dayView.delegate = context.coordinator
}
typealias UIViewControllerType = DayViewController
class Coordinator: NSObject, DayViewDelegate {
weak var viewController: DayViewController? // << declare here !!
var parent: CalendarDayView
init(_ parent: CalendarDayView) {
self.parent = parent
}
func dayViewDidSelectEventView(_ eventView: EventView) {
guard let ckEvent = eventView.descriptor as? EKWrapper else { return }
let ekEvent = ckEvent.ekEvent
viewController?.endEventEditing() // << call !!
}
}
}

How can I update the Observable Object from an ARView extension in SwiftUI?

I am trying to Update the globalDataTransfer class from the ARView extension and reflect the changes on to ArView. Below is the globalDataTransfer function
class globalDataTransfer: ObservableObject{
#Published var val: String = "No Value"
required init (any : String){
self.val = any
}
func check() -> String{
return(self.val)
}
}
My ARViewContainer
struct ARViewContainer: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<ARViewContainer>) -> ARView {
let arView = ARView(frame: .zero,cameraMode: .ar,automaticallyConfigureSession: true)
arView.setupForBodyTracking()
arView.scene.addAnchor(bodySkeletonAnchor)
return(arView)
}
func updateUIView(_ uiView: ARView, context: Context) {
}
typealias UIViewType = ARView
}
My ARView extension is
extension ARView: ARSessionDelegate{
func setupForBodyTracking(){
let config = ARBodyTrackingConfiguration()
self.session.run(config)
self.session.delegate = self
}
public func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
for anchor in anchors{
if let bodyAnchor = anchor as? ARBodyAnchor{
if let skeleton = bodySkeleton{
skeleton.update(with: bodyAnchor)
var
}
else{
let skeleton = BodySkeleton(for: bodyAnchor)
bodySkeleton = skeleton
bodySkeletonAnchor.addChild(skeleton)
}
var Val:String = calcAngle(anchor: bodyAnchor) //Function that returns Joint Angles and lengths
}
}
}
}
Content View :
struct ArView: View {
#ObservedObject var forGlobalValue: globalDataTransfer = globalDataTransfer(any: "No Value")
var body: some View{
ZStack{
ARViewContainer()
VStack{
Text(forGlobalValue.val)
Text(" This is \(test(any: forGlobalValue))")
}
}
}
}
Can anyone let me know How do I pass this Observable object to the ARView or is there any way of getting the Val variable from ARView extension and update it regularly on my View Text.

Handle keyboard inputs in NSTextView embedded via NSViewRepresentable? (SwiftUI/MacOS)

I'm new to SwiftUI and am utterly confused.. I managed to embed a NSTextView into my SwiftUI View and bind its text with the below code.
What I don't understand; is there a way to handle keyboard inputs to the NSTextView and change its text accordingly (e.g. CMD + R sets the text color of the selected text to red)? Is there even any way to interact with UI-Elements in SwiftUI?
"RichTextField"
struct RichTextField: NSViewRepresentable {
typealias NSViewType = NSTextView
#Binding var attributedString: NSAttributedString
func makeNSView(context: Context) -> NSTextView {...
// [...]
}
View
struct EditWindow: View {
#ObservedObject var model: EditEntryViewModel
#Binding var isPresented: Bool
var body: some View {
RichTextField(attributedString: self.$model.answer1, isEditable: true)
// [...]
}
}
Furthermore, I've managed to set up a menu command in the AppDelegate, but how could I use this to change the text (at a certain position) in a NSTextView of an arbitrary View?
#IBAction func setTagImportant(_ sender: Any) {
print("setTagImportant")
}
Thanks a lot for shedding some light on this for me...
Ironically, immediately after finally posting this question, I found a solution; simply subclass the NSTextView and then override keyDown:
import SwiftUI
class RichTextFieldExtended: NSTextView {
override func keyDown(with event: NSEvent) {
if event.modifierFlags.contains(NSEvent.ModifierFlags.command) {
switch event.keyCode {
case 18: // 1
print("1 PRESSED")
default:
print("keyCode \(event.keyCode) wasn't handled")
super.keyDown(with: event)
}
} else {
super.keyDown(with: event)
}
}
}
Then include the subclassed NSTextView in the NSViewRepresentable, as follows
struct RichTextField: NSViewRepresentable {
typealias NSViewType = RichTextFieldExtended
#Binding var attributedString: NSAttributedString
var isEditable: Bool
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeNSView(context: Context) -> RichTextFieldExtended {
let textView = RichTextFieldExtended(frame: .zero)
textView.textStorage?.setAttributedString(self.attributedString)
textView.isEditable = isEditable
textView.delegate = context.coordinator
textView.translatesAutoresizingMaskIntoConstraints = false
textView.autoresizingMask = [.width, .height]
return textView
}
func updateNSView(_ nsView: RichTextFieldExtended, context: Context) {
// nsView.textStorage!.setAttributedString(self.attributedString)
}
// Source: https://medium.com/fantageek/use-xib-de9d8a295757
class Coordinator: NSObject, NSTextViewDelegate {
let parent: RichTextField
init(_ RichTextField: RichTextField) {
self.parent = RichTextField
}
func textDidChange(_ notification: Notification) {
guard let textView = notification.object as? RichTextFieldExtended else { return }
self.parent.attributedString = textView.attributedString()
}
}
}
Cheers

IGListKitSections doesn't get deallocated

I have a problem with IGListKit sections deallocating. Trying to debug the issue with Xcode memory graph.
My setup is AuthController -> AuthViewModel -> AuthSocialSectionController -> AuthSocialViewModel and some other sections.
AuthController gets presented from several parts of the app if user is not logged in. When I tap close, AuthViewModel and AuthController gets deallocated, but it's underlying sections does not. Memory graph shows nothing leaked in this case, but deinit methods doesn't get called.
But when I'm trying to authorize with social account (successfully) and then look at the memory graph, it shows that sections, that doesn't get deallocated like this:
In this case AuthViewModel doesn't get deallocated either, but after some time it does, but it can happen or not.
I checked every closure and delegate for weak reference, but still no luck.
My code, that I think makes most sense:
class AuthViewController: UIViewController {
fileprivate let collectionView: UICollectionView = UICollectionView(frame: .zero,
collectionViewLayout: UICollectionViewFlowLayout())
lazy var adapter: ListAdapter
= ListAdapter(updater: ListAdapterUpdater(), viewController: self, workingRangeSize: 0)
fileprivate lazy var previewProxy: SJListPreviewProxy = {
SJListPreviewProxy(adapter: adapter)
}()
fileprivate let viewModel: AuthViewModel
fileprivate let disposeBag = DisposeBag()
init(with viewModel: AuthViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
hidesBottomBarWhenPushed = true
setupObservers()
}
private func setupObservers() {
NotificationCenter.default.rx.notification(.SJAProfileDidAutoLogin)
.subscribe(
onNext: { [weak self] _ in
self?.viewModel.didSuccessConfirmationEmail()
self?.viewModel.recoverPasswordSuccess()
})
.disposed(by: disposeBag)
}
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
// MARK: - Private
#objc private func close() {
dismiss(animated: true, completion: nil)
}
/// Метод настройки экрана
private func setup() {
if isForceTouchEnabled() {
registerForPreviewing(with: previewProxy, sourceView: collectionView)
}
view.backgroundColor = AppColor.instance.gray
title = viewModel.screenName
let item = UIBarButtonItem(image: #imageLiteral(resourceName: "close.pdf"), style: .plain, target: self, action: #selector(AuthViewController.close))
item.accessibilityIdentifier = "auth_close_btn"
asViewController.navigationItem.leftBarButtonItem = item
navigationItem.titleView = UIImageView(image: #imageLiteral(resourceName: "logo_superjob.pdf"))
collectionViewSetup()
}
// Настройка collectionView
private func collectionViewSetup() {
collectionView.keyboardDismissMode = .onDrag
collectionView.backgroundColor = AppColor.instance.gray
view.addSubview(collectionView)
adapter.collectionView = collectionView
adapter.dataSource = self
collectionView.snp.remakeConstraints { make in
make.edges.equalToSuperview()
}
}
}
// MARK: - DataSource CollectionView
extension AuthViewController: ListAdapterDataSource {
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
return viewModel.sections(for: listAdapter)
}
func listAdapter(_: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
return viewModel.createListSectionController(for: object)
}
func emptyView(for _: ListAdapter) -> UIView? {
return nil
}
}
// MARK: - AuthViewModelDelegate
extension AuthViewController: AuthViewModelDelegate {
func hideAuth(authSuccessBlock: AuthSuccessAction?) {
dismiss(animated: true, completion: {
authSuccessBlock?()
})
}
func reload(animated: Bool, completion: ((Bool) -> Void)? = nil) {
adapter.performUpdates(animated: animated, completion: completion)
}
func showErrorPopover(with item: CommonAlertPopoverController.Item,
and anchors: (sourceView: UIView, sourceRect: CGRect)) {
let popover = CommonAlertPopoverController(with: item,
preferredContentWidth: view.size.width - 32.0,
sourceView: anchors.sourceView,
sourceRect: anchors.sourceRect,
arrowDirection: .up)
present(popover, animated: true, completion: nil)
}
}
class AuthViewModel {
fileprivate let assembler: AuthSectionsAssembler
fileprivate let router: AuthRouter
fileprivate let profileFacade: SJAProfileFacade
fileprivate let api3ProfileFacade: API3ProfileFacade
fileprivate let analytics: AnalyticsProtocol
fileprivate var sections: [Section] = []
weak var authDelegate: AuthDelegate?
weak var vmDelegate: AuthViewModelDelegate?
var authSuccessBlock: AuthSuccessAction?
private lazy var socialSection: AuthSocialSectionViewModel = { [unowned self] in
self.assembler.socialSection(delegate: self)
}()
init(assembler: AuthSectionsAssembler,
router: AuthRouter,
profileFacade: SJAProfileFacade,
api3ProfileFacade: API3ProfileFacade,
analytics: AnalyticsProtocol,
delegate: AuthDelegate? = nil,
purpose: Purpose) {
self.purpose = purpose
authDelegate = delegate
self.assembler = assembler
self.router = router
self.profileFacade = profileFacade
self.api3ProfileFacade = api3ProfileFacade
self.analytics = analytics
sections = displaySections()
}
private func authDisplaySections() -> [Section] {
let sections: [Section?] = [vacancySection,
authHeaderSection,
socialSection,
authLoginPasswordSection,
signInButtonSection,
switchToSignUpButtonSection,
recoverPasswordSection]
return sections.compactMap { $0 }
}
}
class AuthSocialSectionController: SJListSectionController, SJUpdateCellsLayoutProtocol {
fileprivate let viewModel: AuthSocialSectionViewModel
init(viewModel: AuthSocialSectionViewModel) {
self.viewModel = viewModel
super.init()
minimumInteritemSpacing = 4
viewModel.vmDelegate = self
}
override func cellType(at _: Int) -> UICollectionViewCell.Type {
return AuthSocialCell.self
}
override func cellInitializationType(at _: Int) -> SJListSectionCellInitializationType {
return .code
}
override func configureCell(_ cell: UICollectionViewCell, at index: Int) {
guard let itemCell = cell as? AuthSocialCell else {
return
}
let item = viewModel.item(at: index)
itemCell.imageView.image = item.image
}
override func separationStyle(at _: Int) -> SJCollectionViewCellSeparatorStyle {
return .none
}
}
extension AuthSocialSectionController {
override func numberOfItems() -> Int {
return viewModel.numberOfItems
}
override func didSelectItem(at index: Int) {
viewModel.didSelectItem(at: index)
}
}
// MARK: - AuthSocialSectionViewModelDelegate
extension AuthSocialSectionController: AuthSocialSectionViewModelDelegate {
func sourceViewController() -> UIViewController {
return viewController ?? UIViewController()
}
}
protocol AuthSocialSectionDelegate: class {
func successfullyAuthorized(type: SJASocialAuthorizationType)
func showError(with error: Error)
}
protocol AuthSocialSectionViewModelDelegate: SJListSectionControllerOperationsProtocol, ViewControllerProtocol {
func sourceViewController() -> UIViewController
}
class AuthSocialSectionViewModel: NSObject {
struct Item {
let image: UIImage
let type: SJASocialAuthorizationType
}
weak var delegate: AuthSocialSectionDelegate?
weak var vmDelegate: AuthSocialSectionViewModelDelegate?
fileprivate var items: [Item]
fileprivate let api3ProfileFacade: API3ProfileFacade
fileprivate let analyticsFacade: SJAAnalyticsFacade
fileprivate var socialButtonsDisposeBag = DisposeBag()
init(api3ProfileFacade: API3ProfileFacade,
analyticsFacade: SJAAnalyticsFacade) {
self.api3ProfileFacade = api3ProfileFacade
self.analyticsFacade = analyticsFacade
items = [
Item(image: #imageLiteral(resourceName: "ok_icon.pdf"), type: .OK),
Item(image: #imageLiteral(resourceName: "vk_icon.pdf"), type: .VK),
Item(image: #imageLiteral(resourceName: "facebook_icon.pdf"), type: .facebook),
Item(image: #imageLiteral(resourceName: "mail_icon.pdf"), type: .mail),
Item(image: #imageLiteral(resourceName: "google_icon.pdf"), type: .google),
Item(image: #imageLiteral(resourceName: "yandex_icon.pdf"), type: .yandex)
]
if analyticsFacade.isHHAuthAvailable() {
items.append(Item(image: #imageLiteral(resourceName: "hh_icon"), type: .HH))
}
}
// MARK: - actions
func didSelectItem(at index: Int) {
guard let vc = vmDelegate?.sourceViewController() else {
return
}
let itemType: SJASocialAuthorizationType = items[index].type
socialButtonsDisposeBag = DisposeBag()
api3ProfileFacade.authorize(with: itemType, sourceViewController: vc)
.subscribe(
onNext: { [weak self] _ in
self?.delegate?.successfullyAuthorized(type: itemType)
},
onError: { [weak self] error in
if case let .detailed(errorModel)? = error as? ApplicantError {
self?.vmDelegate?.asViewController.showError(with: errorModel.errors.first?.detail ?? "")
} else {
self?.vmDelegate?.asViewController.showError(with: "Неизвестная ошибка")
}
})
.disposed(by: socialButtonsDisposeBag)
}
}
// MARK: - DataSource
extension AuthSocialSectionViewModel {
var numberOfItems: Int {
return items.count
}
func item(at index: Int) -> Item {
return items[index]
}
}
// MARK: - ListDiffable
extension AuthSocialSectionViewModel: ListDiffable {
func diffIdentifier() -> NSObjectProtocol {
return ObjectIdentifier(self).hashValue as NSObjectProtocol
}
func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
return object is AuthSocialSectionViewModel
}
}
Where assembler is responsible for creating everyting, for example AuthSocialSection:
func socialSection(delegate: AuthSocialSectionDelegate?) -> AuthSocialSectionViewModel {
let vm = AuthSocialSectionViewModel(api3ProfileFacade: api3ProfileFacade,
analyticsFacade: analyticsFacade)
vm.delegate = delegate
return vm
}
How can I properly debug this issue? Any advice or help is really appreciated
Found an issue in AuthSocialSectionController. Somehow passing viewController from IGList context through delegates caused memory issues. When I commented out the viewModel.vmDelegate = self the issue was gone.
That explains why the AuthViewModel was deallocating properly when I hit close button without attempting to authorize. Only when I hit authorize, that viewController property was called.
Thanks for help #vpoltave
This lines from your AuthViewController can this cause leaks?
// adapter has viewController: self
lazy var adapter: ListAdapter
= ListAdapter(updater: ListAdapterUpdater(), viewController: self, workingRangeSize: 0)
fileprivate lazy var previewProxy: SJListPreviewProxy = {
// capture self.adapter ?
SJListPreviewProxy(adapter: adapter)
}()
I'm not sure, but at least you can try :)
UPDATE
I was wondering about this lazy closures and self inside, it won't create retain cycle because lazy initialization are #nonescaping.