Problem
Setting ".environment(.layoutDirection, .rightToLeft)" to a view programmatically won't make buttons in menu to show right to left.
However, setting ".environment(.locale, .init(identifier: "he-IL"))" to a view programmatically makes buttons in menu to show Hebrew strings correctly.
Development environment: Xcode 16.x, macOS 15.3.1
Target iOS: iOS 17 - iOS 18
The expected result is that the button in the menu should be displayed as an icon then a text from left to right.
Code to demonstrate the problem:
struct ContentView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Buttons in menu don't respect the environment value of .layoutDirection")
.font(.subheadline)
.padding(.bottom, 48)
/// This button respects both "he-IL" of ".locale" and ".rightToLeft" of ".layoutDirection".
Button {
print("Button tapped")
} label: {
HStack {
Text("Send")
Image(systemName: "paperplane")
}
}
Menu {
/// This button respects "he-IL" of ".locale" but doesn't respect ".rightToLeft" of ".layoutDirection".
Button {
print("Button tapped")
} label: {
HStack {
Text("Send")
Image(systemName: "paperplane")
}
}
} label: {
Text("Menu")
}
}
.padding()
.environment(\.locale, .init(identifier: "he-IL"))
.environment(\.layoutDirection, .rightToLeft)
}
}
Explore the various UI frameworks available for building app interfaces. Discuss the use cases for different frameworks, share best practices, and get help with specific framework-related questions.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
When the following structure:
NavigationStack {
ScrollView {
NavigationLink(...)
}
}
is presented inside a sheet in SwiftUI, the scroll drag gesture and the link tap gesture collide.
If the user happens to begin a scroll gesture on a link, the link will open the moment the finger is lifted, no matter how far it is from its initial touchdown.
The issue has already been found (this stack overflow post from 2019), but no solution was provided.
I've already filed a report (FB17034020).
In the meantime, is there any way to override the touch gesture detection in any manner? This issue is quite inconvenient from a user perspective.
There appears to be a visual bug when using .searchable in a child view that’s pushed via NavigationLink inside a NavigationStack. Specifically, the search bar appears briefly in the wrong position (or animates in an unexpected way) during the transition to the child view.
This issue does not occur when using NavigationView instead of NavigationStack.
Steps to Reproduce:
Create a TabView with a single tab containing a NavigationStack.
Push from a ContentView to a DetailsView using NavigationLink.
Add a .searchable modifier to both the ContentView and DetailsView.
Run the app and tap a row to navigate to the details view.
Expected Behavior
The search bar in the DetailsView should appear smoothly and in the correct position as the view transitions in, just like it does under NavigationView.
Actual Behavior
When the DetailsView appears, the search bar briefly animates or appears in the wrong location before settling into place. This results in a jarring or buggy visual experience.
Feedback: FB17031212
Here is a reddit thread discussing the issue as well https://www.reddit.com/r/SwiftUI/comments/137epji/navigation_stack_with_search_bar_has_a_bug_and_a/
I hope that an Apple engineer can get this fixed soon. It's frustrating to have new APIs come out with the old deprecated yet there are still obvious bugs two years later.
import SwiftUI
public enum Tab {
case main
}
struct AppTabNavigation: View {
@State private var tabSelection = Tab.main
var body: some View {
TabView(selection: $tabSelection) {
NavigationStack {
ContentView()
}
.tag(Tab.main)
.tabItem {
Label("Main", systemImage: "star")
}
}
}
}
struct ContentView: View {
@State private var searchText = ""
var body: some View {
List(0..<100) { i in
NavigationLink("Select \(i)", value: i)
}
.navigationTitle("Main")
.searchable(text: $searchText)
.navigationDestination(for: Int.self) { i in
DetailsView(i: i)
}
}
}
struct DetailsView: View {
@State private var searchText = ""
let i: Int
// MARK: - Body
var body: some View {
List {
ForEach(0..<10, id: \.self) { i in
Text("Hello \(i)")
}
}
.navigationTitle(i.formatted())
.searchable(text: $searchText)
}
}
Topic:
UI Frameworks
SubTopic:
SwiftUI
Hi!
I am having issues with my internal testing app now showing up the same through different users devices?
I'm trying to determine if it’s possible to detect when a user interacts with a Slide Over window while my app is running in the background on iPadOS. I've explored lifecycle methods such as scenePhase and various UIApplication notifications (e.g., willResignActiveNotification) to detect focus loss, but these approaches don't seem to capture the event reliably. Has anyone found an alternative solution or workaround for detecting this specific state change? Any insights or recommended practices would be greatly appreciated.
Background
I have a SwiftUI app that uses OSLog and the new Logger framework. In my SwiftUI views, I would like to use something like Self._logChanges() to help debug issues.
After some trial and error, I can see the messages appear in the System console log for the app I am debugging using the com.apple.SwiftUI subsystem.
Problem
I'd like to see those same messages directly in Xcode's console window so I can filter them as needed. How do I do that?
Thanks! -Patrick
I was doing an app which had several "camera" buttons each one dedicated to taking/storing/reviewing/deleting an image associated with a variable URL but what should have been a simple no brainer turned out to be a programming nightmare.
To cut a long story short there is a bug in the sheet handling wherebye even tho you have separate instance for each button the camera/picker cylcles sequentially thru the stack of instances for any action finally always placing the image in the first URL. Working with myself debugging, all major AIs (Grok, Claude, Gemini and Perplexity) after 4 x 12hr+ days we finally managed to crack a solution. What follows is Groks interpretation (note it misses the earlier problem of instance cycling!!) ...
You can follow the discussion here: https://x.com/i/grok/share/KHeaUPladURmbFq5qy9W506er
but be warned its long a detailed but if you are having problems then read ...
**Bug Report: Race Conditions with UIImagePickerController in SwiftUI Sheet
**
Environment:
SwiftUI, iOS 17.7.5
Device: iPad Pro (12.9-inch, 2nd generation)
Xcode Version: [Insert your Xcode version]
Date: March 30, 2025
**Issue 1: Multiple Instances of UIImagePickerController Spawned After Dismissal
**
Description:
When using a UIImagePickerController wrapped in a UIViewControllerRepresentable and presented via a SwiftUI .sheet, selecting "Use Photo" resulted in multiple unintended instances of the picker being initialized and presented. The console logs showed repeated "Camera initialized" and "Camera sheet appeared" messages (e.g., multiple <UIImagePickerController: 0x...> instances) after the initial dismissal, despite the sheet being dismissed programmatically.
Reproduction Steps:
Create a SwiftUI view with a button that sets a @State variable showCamera to true.
Present a UIImagePickerController via .sheet(isPresented: $showCamera).
Update a @Binding variable (e.g., photoLocation: URL?) in imagePickerController(_:didFinishPickingMediaWithInfo:) after saving the image.
Dismiss the picker with picker.dismiss(animated: true) and presentationMode.wrappedValue.dismiss().
Observe that updating the @Binding variable triggers a view re-render, causing the .sheet to re-present multiple times before finally staying dismissed.
Root Cause:
A race condition occurred between the view update (triggered by changing photoLocation) and the dismissal of the picker. During the re-render, showCamera remained true momentarily, causing the .sheet modifier to re-evaluate and spawn new picker instances before the onDismiss closure could reset showCamera to false.
The fix involved delaying the @Binding update (photoLocation) until after the picker and sheet were fully dismissed, ensuring showCamera was reset to false before the view re-rendered:
Introduced an onPhotoPicked: (URL) -> Void closure to decouple the photoLocation update from the dismissal timing.
Modified the coordinator to call onPhotoPicked and reset showCamera before initiating dismissal:swift
Issue 2: Single Unintended Picker Reopen After Initial Fix
Description:
After addressing the multiple-instance issue, a single unintended reopen of the picker persisted. The logs showed one additional "Camera initialized" and "Camera sheet appeared" after "Use Photo," before the final dismissal.
Reproduction Steps:
Reproduction Steps:
Use the initial fix with onPhotoPicked and delayed photoLocation update.
Take a photo and select "Use Photo."
Observe one extra picker instance appearing briefly before dismissal completes.
Root Cause:
The @Binding update (photoLocation) was still occurring too early in the dismissal sequence. Although delayed until after picker.dismiss, the view re-render happened while showCamera was still true during the dismissal animation, causing the .sheet to re-present once before onDismiss reset showCamera.
Resolution:
The fix ensured showCamera was set to false before the picker dismissal animation began, preventing the .sheet from re-evaluating during the transition:
Moved the dismissCamera() call (which sets showCamera to false) into the onPhotoPicked callback, executed before picker.dismiss:
CameraView(
photoLocation: $photoLocation,
storeDirectory: storeDirectory,
onPhotoPicked: { url in
print("Photo picked callback for \(id), setting photoLocation: \(url)")
self.photoLocation = url
self.cameraState.dismissCamera() // Sets showCamera to false first
}
)
Kept the dismissal sequence in the coordinator:
DispatchQueue.main.async {
self.parent.onPhotoPicked(fileURL)
picker.dismiss(animated: true) {
self.parent.presentationMode.wrappedValue.dismiss()
}
}
This synchronized the state change with the dismissal, ensuring showCamera was false before the view re-rendered, eliminating the single reopen.
Request:
Could the SwiftUI team clarify if this behavior is expected, or consider improving the .sheet modifier to better handle state transitions during UIKit controller dismissal? A more robust bridge between SwiftUI’s declarative state and UIKit’s imperative lifecycle could prevent such race conditions.
Environment:
• macOS: Sequoia 15.3.2
• Xcode: 16.2
• Framework: AppKit (Objective-C)
Issue:
When programmatically setting the first responder to an NSSecureTextField shortly after its containing window loads or becomes key, a visual anomaly intermittently occurs (roughly 50% of the time).
A semi-transparent UI element—likely related to the system’s Keychain password suggestion/autofill feature—appears detached from the text field. Instead of anchoring to the field, it renders elsewhere on the screen.
I found similar issues discussed here:
https://stackoverflow.com/questions/74220070/strange-transparent-view-appears-beneath-textfield-in-mac-catalyst-app
https://stackoverflow.com/questions/73277582/swiftui-view-with-textfield-and-securefield-buggy-on-macos-shows-strange-view/73615876#73615876
https://developer.apple.com/forums/thread/708075
为什么App 上传testFlight之后。无法通过NFC的方式唤醒 APP Clips。是必须要上架商店之后才能支持么?
I have a few crash report from TestFlight with this context and nothing else. No symbols of my application.
Crash report is attached.
crashlog.crash
It crashes at 16H25 (local time paris) and a few minutes after (8) I see the same user doing task in background (they download data with a particular URL in BGtasks) with a Delay that is consistant with a background wait of 5 or 7 minutes.
I have no more information on this crash and by the way Xcode refuses to load it and shows an error. ( I did a report via Xcode for this).
import SwiftUI
import OsLog
let logger = Logger(subsystem: "Test", category: "Test")
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
.task {
logger.info("Hallo")
}
}
}
#Preview {
ContentView()
}
27 | .padding()
28 | .task {
29 | logger.info(__designTimeString("#6734_2", fallback: "Hallo"))
| `- error: argument must be a string interpolation
30 | }
31 | }
Should OsLog be compatible with __designTimeString?
We've seen a spike in crashes on iOS 18.4 across both iPhone & iPad. We can't reproduce it, but it looks like it happens when the app goes into the background.
Crash Log
UITextField The input space cursor is gone
Topic:
UI Frameworks
SubTopic:
UIKit
I have a Date field that holds the scheduled start date for an activity.. However, activities can be unscheduled (i.e., waiting to be scheduled at some other time). I want to use Date.distantFuture to indicate that the activity is unscheduled. Therefore I am looking to implement logic in my UI that looks something like
@State private var showCalendar: Bool = false
if date == .distantFuture {
Button("unscheduled") {
showCalendar.toggle()
}.buttonStyle(.bordered)
} else {
DatePicker(section: $date)
}
.popover(isPresented: $showCalendar) {
<use DatePicker Calendar view>
}
But this approach requires that I access the DataPicker's Calendar view and I don't know how to do that (and I don't ever what my users to see "Dec 31, 4000"). Any ideas?
(BTW, I have a UIKit Calendar control I could use, but I'd prefer to use the standard control if possible.)
I want use SensorKit data for research purposes in my current app.
I have applied for and received permission from Apple to access SensorKit Data. I have granting all the necessary permissions. But no data retrieved.
I am using didCompleteFetch for retrieving data from Sensorkit. CompleteFetch method calls but find the data. Below is my SensorKitManager Code.
import SensorKit
import Foundation
protocol SensorManagerDelegate: AnyObject {
func didFetchPhoneUsageReport(_ reports: [SRPhoneUsageReport])
func didFetchAmbientLightSensorData(_ data: [SRAmbientLightSample])
func didFailFetchingData(error: Error)
}
class SensorManager: NSObject, SRSensorReaderDelegate {
private let phoneUsageReader: SRSensorReader
private let ambientLightReader: SRSensorReader
weak var delegate: SensorManagerDelegate?
override init() {
self.phoneUsageReader = SRSensorReader(sensor: .phoneUsageReport)
self.ambientLightReader = SRSensorReader(sensor: .ambientLightSensor)
super.init()
self.phoneUsageReader.delegate = self
self.ambientLightReader.delegate = self
}
func requestAuthorization() {
let sensors: Set<SRSensor> = [.phoneUsageReport, .ambientLightSensor]
guard phoneUsageReader.authorizationStatus != .authorized || ambientLightReader.authorizationStatus != .authorized else {
log("Already authorized. Fetching data directly...")
fetchSensorData()
return
}
SRSensorReader.requestAuthorization(sensors: sensors) { [weak self] error in
DispatchQueue.main.async {
if let error = error {
self?.log("Authorization failed: \(error.localizedDescription)", isError: true)
self?.delegate?.didFailFetchingData(error: error)
} else {
self?.log("Authorization granted.")
self?.fetchSensorData()
}
}
}
}
func fetchSensorData() {
guard let fromDate = Calendar.current.date(byAdding: .day, value: -1, to: Date()) else {
log("Failed to calculate 'from' date.", isError: true)
return
}
let fromTime = SRAbsoluteTime.fromCFAbsoluteTime(_cf: fromDate.timeIntervalSinceReferenceDate)
let toTime = SRAbsoluteTime.fromCFAbsoluteTime(_cf: Date().timeIntervalSinceReferenceDate)
let phoneUsageRequest = SRFetchRequest()
phoneUsageRequest.from = fromTime
phoneUsageRequest.to = toTime
phoneUsageRequest.device = SRDevice.current
let ambientLightRequest = SRFetchRequest()
ambientLightRequest.from = fromTime
ambientLightRequest.to = toTime
ambientLightRequest.device = SRDevice.current
phoneUsageReader.fetch(phoneUsageRequest)
ambientLightReader.fetch(ambientLightRequest)
}
// ✅ Delegate Methods
func sensorReader(_ reader: SRSensorReader, didCompleteFetch fetchRequest: SRFetchRequest) {
Task.detached {
if reader.sensor == .phoneUsageReport {
if let samples = reader.fetch(fetchRequest) as? [SRPhoneUsageReport] {
DispatchQueue.main.async { [weak self] in
self?.delegate?.didFetchPhoneUsageReport(samples)
}
}
} else if reader.sensor == .ambientLightSensor {
if let samples = reader.fetch(fetchRequest) as? [SRAmbientLightSample] {
DispatchQueue.main.async { [weak self] in
self?.delegate?.didFetchAmbientLightSensorData(samples)
}
}
}
}
}
func sensorReader(_ reader: SRSensorReader, fetching fetchRequest: SRFetchRequest, didFetchResult result: SRFetchResult<AnyObject>) -> Bool {
return true
}
func sensorReader(_ reader: SRSensorReader, fetching fetchRequest: SRFetchRequest, failedWithError error: any Error) {
DispatchQueue.main.async { [weak self] in
self?.delegate?.didFailFetchingData(error: error)
}
}
// MARK: - Logging Helper
private func log(_ message: String, isError: Bool = false) {
if isError {
print("❌ [SensorManager] \(message)")
} else {
print("✅ [SensorManager] \(message)")
}
}
}
And ViewController
import UIKit
import SensorKit
class ViewController: UIViewController {
private var sensorManager: SensorManager!
override func viewDidLoad() {
super.viewDidLoad()
setupSensorManager()
}
private func setupSensorManager() {
sensorManager = SensorManager()
sensorManager.delegate = self
sensorManager.requestAuthorization()
}
}
// MARK: - SensorManagerDelegate
extension ViewController: SensorManagerDelegate {
func didFetchPhoneUsageReport(_ reports: [SRPhoneUsageReport]) {
for report in reports {
print("Total Calls: (report.totalOutgoingCalls + report.totalIncomingCalls)")
print("Outgoing Calls: (report.totalOutgoingCalls)")
print("Incoming Calls: (report.totalIncomingCalls)")
print("Total Call Duration: (report.totalPhoneCallDuration) seconds")
}
}
func didFetchAmbientLightSensorData(_ data: [SRAmbientLightSample]) {
for sample in data {
print(sample)
}
}
func didFailFetchingData(error: Error) {
print("Failed to fetch data: \(error.localizedDescription)")
}
}
Could anyone please assist me in resolving this issue? Any guidance or troubleshooting steps would be greatly appreciated.
I have the following UIViewRepresentable that loads a webview.
struct SViewerWebView: UIViewRepresentable{
var url: String
var token: String
@Binding var isLoading: Bool
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> WKWebView {
let webConfiguration = WKWebViewConfiguration()
let webView = WKWebView(frame:.zero,configuration:webConfiguration)
webView.allowsBackForwardNavigationGestures = true
webView.isInspectable = true
webView.navigationDelegate = context.coordinator
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {
guard let urlforRequest = URL(string: url) else {
print("❌ Invalid URL:", url)
return
}
var request = URLRequest(url: urlforRequest)
request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
print("🔄 Loading URL:", url)
print("🛠 Headers:", request.allHTTPHeaderFields ?? [:])
uiView.load(request)
}
//coordinator
class Coordinator: NSObject, WKNavigationDelegate {
var parent: SViewerWebView
init(_ parent: SViewerWebView) {
self.parent = parent
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
DispatchQueue.main.async {
self.parent.isLoading = false // Hide loading when page loads
}
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
DispatchQueue.main.async {
self.parent.isLoading = false // Hide loading on error
}
}
}
}
This is the state before the content loads. At this point a ProgressView() is displayed:
The problem comes in the step between screenshot 1 and 3:
as you can see in below pictures, before navigating to the webview content, there is a default loading text that still showing up. Apparently, it seems to be the default behavior from the wkwebview. How can I hide that text status?
my view component:
var body: some View {
ZStack{
SViewerWebView(url: webUrl,token: TokenManager.getToken()!,isLoading: $isLoading)
if isLoading{
VStack {
ProgressView()
.progressViewStyle(CircularProgressViewStyle())
.scaleEffect(1.5)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.white)
}
}
.ignoresSafeArea()
}
Topic:
UI Frameworks
SubTopic:
SwiftUI
It appears that on all recent versions of macOS when adding a new InputSource in /Library/Input Methods (or modifying an existing one there) the user needs to logoff and log back in in order for Keyboard/Input Sources in System Settings and Input Menu in menu bar to pick up the changes.
Is there a way to avoid this? That is, some notification to send or API to call to tell both of these "hey, things might have changed on disk, please re-read the info, and update the UI". 🙂
When i have TextField inside ScrollView and tap on it the keyboard is shown as expected. But it seems that the TextField is moved up just enough to show the input area but i want to be moved enough so that is visible in its whole. Otherwise it looks cropped. I couldn't find a way to change this behaviour.
struct ContentView: View {
@State var text:String = ""
var body: some View {
ScrollView {
VStack(spacing: 10) {
ForEach(1...12, id: \.self) {
Text("\($0)…")
.frame(height:50)
}
TextField("Label..", text: self.$text)
.padding(10)
.background(.white)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(.blue, lineWidth: 1)
)
}
.padding()
.background(.red)
}
}
}
Issue Description
Whenever the first item in the List is a DisclosureGroup, all subsequent disclosure groups work fine. However, if the first item is not a disclosure group, the disclosure groups in subsequent items do not render correctly.
This issue does not occur in macOS 15, where everything works as expected.
Has anyone else encountered this behavior, or does anyone have a workaround for macOS 13 & 14?
I’m not using OutlineGroup because I need to bind to an isExpanded property for each row in the list.
Reproduction Steps
I’ve created a small test project to illustrate the issue:
Press “Insert item at top” to add a non-disclosure item at the start of the list.
Then, press “Append item with sub-item” to add a disclosure group further down.
The disclosure group does not display correctly. The label of the disclosure group renders fine, but the content of the disclosure group does not display at all.
Press "Insert item at top with sub-item" and the list displays as expected.
Build Environment
macOS 15.3.2 (24D81)
Xcode Version 16.2 (16C5032a)
Issue Observed
macOS 13 & 14 (bug occurs)
macOS 15 (works correctly)
Sample Code
import SwiftUI
class ListItem: ObservableObject, Hashable, Identifiable {
var id = UUID()
@Published var name: String
@Published var subItems: [ListItem]?
@Published var isExpanded: Bool = true
init(
name: String,
subjobs: [ListItem]? = nil
) {
self.name = name
self.subItems = subjobs
}
static func == (lhs: ListItem, rhs: ListItem) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
struct ContentView: View {
@State private var listItems: [ListItem] = []
@State private var selectedJob: ListItem?
@State private var redraw: Int = 0
var body: some View {
VStack {
List(selection: $selectedJob) {
ForEach(self.listItems, id: \.id) { job in
self.itemRowView(for: job)
}
}
.id(redraw)
Button("Insert item at top") {
self.listItems.insert(
ListItem(
name: "List item \(listItems.count)"
),
at: 0
)
}
Button("Insert item at top with sub-item") {
self.listItems.insert(
ListItem(
name: "List item \(listItems.count)",
subjobs: [ListItem(name: "Sub-item")]
),
at: 0
)
}
Button("Append item") {
self.listItems.append(
ListItem(
name: "List item \(listItems.count)"
)
)
}
Button("Append item with sub-item") {
self.listItems.append(
ListItem(
name: "List item \(listItems.count)",
subjobs: [ListItem(name: "Sub-item")]
)
)
}
Button("Clear") {
self.listItems.removeAll()
}
Button("Redraw") {
self.redraw += 1
}
}
}
@ViewBuilder
private func itemRowView(for job: ListItem) -> some View {
if job.subItems == nil {
self.itemLabelView(for: job)
} else {
AnyView(
erasing: ListItemDisclosureGroup(job: job) {
self.itemLabelView(for: job)
} jobRowView: { child in
self.itemRowView(for: child)
}
)
}
}
@ViewBuilder private func itemLabelView(for job: ListItem) -> some View {
Text(job.name)
}
struct ListItemDisclosureGroup<LabelView: View, RowView: View>: View {
@ObservedObject var job: ListItem
@ViewBuilder let labelView: () -> LabelView
@ViewBuilder let jobRowView: (ListItem) -> RowView
var body: some View {
DisclosureGroup(isExpanded: $job.isExpanded) {
if let children = job.subItems {
ForEach(children, id: \.id) { child in
self.jobRowView(child)
}
}
} label: {
self.labelView()
}
}
}
}
demo code :
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
NSDictionary *attrs = @{NSFontAttributeName: [UIFont systemFontOfSize:20],
NSForegroundColorAttributeName: [UIColor blueColor],
NSUnderlineStyleAttributeName: @(NSUnderlineStyleThick),
};
// Make an attributed string
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:@"Hello CoreText!" attributes:attrs];
CFAttributedStringRef attributedStringRef = (__bridge CFAttributedStringRef)attributedString;
// Simple CoreText with CTFrameDraw
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attributedStringRef);
CGPathRef path = CGPathCreateWithRect(self.bounds,NULL);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,CFRangeMake(0, 0),path,NULL);
//CTFrameDraw(frame, context);
// You can comment the line 'CTFrameDraw' and use the following lines
// draw with CTLineDraw
CFArrayRef lines = CTFrameGetLines(frame);
CGPoint lineOrigins[CFArrayGetCount(lines)];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), lineOrigins);
for (int i = 0; i < CFArrayGetCount(lines); i++) {
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGContextSetTextPosition(context, lineOrigins[i].x, lineOrigins[i].y);
// CTLineDraw(line, context);
// You can comment the line 'CTLineDraw' and use the following lines
// draw with CTRunDraw
// use CTRunDraw will lost some attributes like NSUnderlineStyleAttributeName,
// so you need draw it by yourself
CFArrayRef runs = CTLineGetGlyphRuns(line);
for (int j = 0; j < CFArrayGetCount(runs); j++) {
CTRunRef run = CFArrayGetValueAtIndex(runs, j);
CTRunDraw(run, context, CFRangeMake(0, 0));
}
}
}
this code will use CTRunDraw to draw the content , and the underline will draw and show normally in iOS17 & Xcode 15 , But when you build it with XCode16 & iOS18 beta . the underline will be missing .