•  


GitHub - siteline/swiftui-introspect: Introspect underlying UIKit/AppKit components from SwiftUI
Skip to content

siteline/swiftui-introspect

Repository files navigation

SwiftUI Introspect

CI Status Badge Swift Version Compatibility Badge Platform Compatibility Badge

SwiftUI Introspect allows you to get the underlying UIKit or AppKit element of a SwiftUI view.

For instance, with SwiftUI Introspect you can access UITableView to modify separators, or UINavigationController to customize the tab bar.

How it works

SwiftUI Introspect works by adding an invisible IntrospectionView on top of the selected view, and an invisible "anchor" view underneath it, then looking through the UIKit/AppKit view hierarchy between the two to find the relevant view.

For instance, when introspecting a ScrollView ...

ScrollView
 {

    Text
(
"
Item 1
"
)

}

.
introspect
(
.
scrollView
,
 on
:
 .
iOS
(
.
v13
,
 .
v14
,
 .
v15
,
 .
v16
,
 .
v17
)
)
 {
 scrollView 
in

    // do something with UIScrollView

}

... it will:

  1. Add marker views in front and behind ScrollView .
  2. Traverse through all subviews between both marker views until a UIScrollView instance (if any) is found.

Important

Although this introspection method is very solid and unlikely to break in itself, future OS releases require explicit opt-in for introspection ( .iOS(.vXYZ) ), given potential differences in underlying UIKit/AppKit view types between major OS versions.

By default, the .introspect modifier acts directly on its receiver . This means calling .introspect from inside the view you're trying to introspect won't have any effect. However, there are times when this is not possible or simply too inflexible, in which case you can introspect an ancestor , but you must opt into this explicitly by overriding the introspection scope :

ScrollView
 {

    Text
(
"
Item 1
"
)

        .
introspect
(
.
scrollView
,
 on
:
 .
iOS
(
.
v13
,
 .
v14
,
 .
v15
,
 .
v16
,
 .
v17
)
,
 scope
:
 .
ancestor
)
 {
 scrollView 
in

            // do something with UIScrollView

        }

}

Usage in production

SwiftUI Introspect is meant to be used in production. It does not use any private API. It only inspects the view hierarchy using publicly available methods. The library takes a defensive approach to inspecting the view hierarchy: there is no hard assumption that elements are laid out a certain way, there is no force-cast to UIKit/AppKit classes, and the .introspect modifier is simply ignored if UIKit/AppKit views cannot be found.

Install

Swift Package Manager

Xcode

Package.swift

let
 package
 =
 Package
(

    dependencies
:
 [

        .
package
(
url
:
 "
https://github.com/siteline/swiftui-introspect
"
,
 from
:
 "
1.0.0
"
)
,

    ]
,

    targets
:
 [

        .
target
(
name
:
 <
#Target Name#
>
,
 dependencies
:
 [

            .
product
(
name
:
 "
SwiftUIIntrospect
"
,
 package
:
 "
swiftui-introspect
"
)
,

        ]
)
,

    ]

)

CocoaPods

pod
 'SwiftUIIntrospect'
,
 '~> 1.0'

Introspection

Implemented

Missing an element? Please start a discussion . As a temporary solution, you can implement your own introspectable view type .

Cannot implement

SwiftUI Affected Frameworks Why
Text UIKit, AppKit Not a UILabel / NSLabel
Image UIKit, AppKit Not a UIImageView / NSImageView
Button UIKit Not a UIButton

Examples

List

List
 {

    Text
(
"
Item
"
)

}

.
introspect
(
.
list
,
 on
:
 .
iOS
(
.
v13
,
 .
v14
,
 .
v15
)
)
 {
 tableView 
in

    tableView
.
backgroundView 
=
 UIView
(
)

    tableView
.
backgroundColor 
=
 .
cyan
}

.
introspect
(
.
list
,
 on
:
 .
iOS
(
.
v16
,
 .
v17
)
)
 {
 collectionView 
in

    collectionView
.
backgroundView 
=
 UIView
(
)

    collectionView
.
subviews
.
dropFirst
(
1
)
.
first
?
.
backgroundColor 
=
 .
cyan
}

ScrollView

ScrollView
 {

    Text
(
"
Item
"
)

}

.
introspect
(
.
scrollView
,
 on
:
 .
iOS
(
.
v13
,
 .
v14
,
 .
v15
,
 .
v16
,
 .
v17
)
)
 {
 scrollView 
in

    scrollView
.
backgroundColor 
=
 .
red
}

NavigationView

NavigationView
 {

    Text
(
"
Item
"
)

}

.
navigationViewStyle
(
.
stack
)

.
introspect
(
.
navigationView
(
style
:
 .
stack
)
,
 on
:
 .
iOS
(
.
v13
,
 .
v14
,
 .
v15
,
 .
v16
,
 .
v17
)
)
 {
 navigationController 
in

    navigationController
.
navigationBar
.
backgroundColor 
=
 .
cyan
}

TextField

TextField
(
"
Text Field
"
,
 text
:
 <
#Binding
<
String
>
#
>
)

    .
introspect
(
.
textField
,
 on
:
 .
iOS
(
.
v13
,
 .
v14
,
 .
v15
,
 .
v16
,
 .
v17
)
)
 {
 textField 
in

        textField
.
backgroundColor 
=
 .
red
    
}

Advanced usage

Implement your own introspectable type

Missing an element? Please start a discussion .

In case SwiftUI Introspect (unlikely) doesn't support the SwiftUI element that you're looking for, you can implement your own introspectable type.

For example, here's how the library implements the introspectable TextField type:

import SwiftUI
@
_spi
(
Advanced
)
 import SwiftUIIntrospect

public
 struct
 TextFieldType
:
 IntrospectableViewType
 {
}


extension
 IntrospectableViewType
 where
 Self 
==
 TextFieldType
 {

    public
 static
 var
 textField
:
 Self
 {
 .
init
(
)
 }

}


#if canImport(UIKit)

extension
 iOSViewVersion
<
TextFieldType
,
 UITextField
>
 {

    public
 static
 let
 v13
 =
 Self
(
for
:
 .
v13
)

    public
 static
 let
 v14
 =
 Self
(
for
:
 .
v14
)

    public
 static
 let
 v15
 =
 Self
(
for
:
 .
v15
)

    public
 static
 let
 v16
 =
 Self
(
for
:
 .
v16
)

    public
 static
 let
 v17
 =
 Self
(
for
:
 .
v17
)

}


extension
 tvOSViewVersion
<
TextFieldType
,
 UITextField
>
 {

    public
 static
 let
 v13
 =
 Self
(
for
:
 .
v13
)

    public
 static
 let
 v14
 =
 Self
(
for
:
 .
v14
)

    public
 static
 let
 v15
 =
 Self
(
for
:
 .
v15
)

    public
 static
 let
 v16
 =
 Self
(
for
:
 .
v16
)

    public
 static
 let
 v17
 =
 Self
(
for
:
 .
v17
)

}


extension
 visionOSViewVersion
<
TextFieldType
,
 UITextField
>
 {

    public
 static
 let
 v1
 =
 Self
(
for
:
 .
v1
)

}

#elseif canImport(AppKit)

extension
 macOSViewVersion
<
TextFieldType
,
 NSTextField
>
 {

    public
 static
 let
 v10_15
 =
 Self
(
for
:
 .
v10_15
)

    public
 static
 let
 v11
 =
 Self
(
for
:
 .
v11
)

    public
 static
 let
 v12
 =
 Self
(
for
:
 .
v12
)

    public
 static
 let
 v13
 =
 Self
(
for
:
 .
v13
)

    public
 static
 let
 v14
 =
 Self
(
for
:
 .
v14
)

}

#endif

Introspect on future platform versions

By default, introspection applies per specific platform version. This is a sensible default for maximum predictability in regularly maintained codebases, but it's not always a good fit for e.g. library developers who may want to cover as many future platform versions as possible in order to provide the best chance for long-term future functionality of their library without regular maintenance.

For such cases, SwiftUI Introspect offers range-based platform version predicates behind the Advanced SPI:

import SwiftUI
@
_spi
(
Advanced
)
 import SwiftUIIntrospect

struct
 ContentView
:
 View
 {

    var
 body
:
 some
 View
 {

        ScrollView
 {

            // ...

        }

        .
introspect
(
.
scrollView
,
 on
:
 .
iOS
(
.
v13
...
)
)
 {
 scrollView 
in

            // ...

        }

    }

}

Bear in mind this should be used cautiously, and with full knowledge that any future OS version might break the expected introspection types unless explicitly available. For instance, if in the example above hypothetically iOS 18 stops using UIScrollView under the hood, the customization closure will never be called on said platform.

Keep instances outside the customize closure

Sometimes, you might need to keep your introspected instance around for longer than the customization closure lifetime. In such cases, @State is not a good option because it produces retain cycles. Instead, SwiftUI Introspect offers a @Weak property wrapper behind the Advanced SPI:

import SwiftUI
@
_spi
(
Advanced
)
 import SwiftUIIntrospect

struct
 ContentView
:
 View
 {

    @
Weak
 var
 scrollView
:
 UIScrollView
?


    var
 body
:
 some
 View
 {

        ScrollView
 {

            // ...

        }

        .
introspect
(
.
scrollView
,
 on
:
 .
iOS
(
.
v13
,
 .
v14
,
 .
v15
,
 .
v16
,
 .
v17
)
)
 {
 scrollView 
in

            self
.
scrollView 
=
 scrollView
        
}

    }

}

Community projects

Here's a list of open source libraries powered by the SwiftUI Introspect library:

If you're working on a library built on SwiftUI Introspect or know of one, feel free to submit a PR adding it to the list.

- "漢字路" 한글한자자동변환 서비스는 교육부 고전문헌국역지원사업의 지원으로 구축되었습니다.
- "漢字路" 한글한자자동변환 서비스는 전통문화연구회 "울산대학교한국어처리연구실 옥철영(IT융합전공)교수팀"에서 개발한 한글한자자동변환기를 바탕하여 지속적으로 공동 연구 개발하고 있는 서비스입니다.
- 현재 고유명사(인명, 지명등)을 비롯한 여러 변환오류가 있으며 이를 해결하고자 많은 연구 개발을 진행하고자 하고 있습니다. 이를 인지하시고 다른 곳에서 인용시 한자 변환 결과를 한번 더 검토하시고 사용해 주시기 바랍니다.
- 변환오류 및 건의,문의사항은 juntong@juntong.or.kr로 메일로 보내주시면 감사하겠습니다. .
Copyright ⓒ 2020 By '전통문화연구회(傳統文化硏究會)' All Rights reserved.
 한국   대만   중국   일본