Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI has function that enables to set tag on View how to acces it

Tags:

swift

swiftui

In SwiftUI there is this function and I can assign tag to View

func tag<V>(_ tag: V) -> some View where V : Hashable

Is there possibility to access this tag like so

MenuItem().tag(1)

and then in MenuItem I have some Button(action: ..., label: ...) and would like to access tag of Button

self.tag

I know I can just pass to MenuItem(tag: Int) like this but consider whether I can achieve something similar like in TabView where each tabItem has tag() assigned to it. How it uses it?

like image 481
Michał Ziobro Avatar asked Nov 22 '19 11:11

Michał Ziobro


2 Answers

An answer of sorts: afaik you can't get the tag, but you can get an index from the local context. For example:

ForEach(0..<2, id: \.self) { buttonIndex in
    Button(action: {
        print("MY NUMBAH BE:", buttonIndex)
    }) {
        Text("WHAT MY NUMBAH?")
    }
}
like image 119
Joseph Beuys' Mum Avatar answered Jun 11 '23 12:06

Joseph Beuys' Mum


I saw this question scrolling through the 'Unanswered' questions here on SO, and so I thought I would try solve it!

I solved this with:

  • Mirror for reflecting views to get a representation of the instance.
  • withUnsafeBytes(_:) to convert types we don't have access to.

All the code is on GitHub at GeorgeElsham/TagExtractor, fully documented. You can easily add the Swift Package to your project. For keeping it short here, the documentation has been removed so this here is mostly just a rough overview of how it works.

Code:

extension View {
    func getTag<TagType: Hashable>() throws -> TagType {
        // Mirror this view
        let mirror = Mirror(reflecting: self)

        // Get tag modifier
        guard let realTag = mirror.descendant("modifier", "value") else {
            // Not found tag modifier here, this could be composite
            // view. Check for modifier directly on the `body` if
            // not a primitive view type.
            guard Body.self != Never.self else {
                throw TagError.notFound
            }
            return try body.getTag()
        }

        // Bind memory to extract tag's value
        let fakeTag = try withUnsafeBytes(of: realTag) { ptr -> FakeTag<TagType> in
            let binded = ptr.bindMemory(to: FakeTag<TagType>.self)
            guard let mapped = binded.first else {
                throw TagError.other
            }
            return mapped
        }

        // Return tag's value
        return fakeTag.value
    }

    func extractTag<TagType: Hashable>(_ closure: (() throws -> TagType) -> Void) -> Self {
        closure(getTag)
        return self
    }
}

enum TagError: Error, CustomStringConvertible {
    case notFound
    case other

    public var description: String {
        switch self {
        case .notFound: return "Not found"
        case .other: return "Other"
        }
    }
}

enum FakeTag<TagType: Hashable> {
    case tagged(TagType)

    var value: TagType {
        switch self {
        case let .tagged(value): return value
        }
    }
}

The documentation on the methods in the repo explain how to use this more in-depth, but here is an overview of 2 examples.

Example #1

let view = Text("Hello world!").tag("some tag")

// Method #1
do {
    let tag: String = try view.getTag()
    print("tag: \(tag)")
} catch {
    fatalError("Tag error: \(error)")
}

// Method #2
let tag: String? = try? view.getTag()
print("tag: \(tag)")

Example #2

Text("Hello world!")
    .tag("some tag")
    .extractTag { (getTag: () throws -> String) in
        // Method #1
        do {
            let tag = try getTag()
            print("tag: \(tag)")
        } catch {
            print("Tag error: \(error)")
        }
    
        // Method #2
        let tag = try? getTag()
        print("tag: \(tag)")
    }
like image 26
George Avatar answered Jun 11 '23 12:06

George