Là một iOS developer chắc hẳn các bạn không lạ gì với Cocoa MVC. Nó được coi là một trong những architecture pattern để phát triển ứng dụng iOS phổ biến nhất. Nó rất dễ sử dụng và được chính Apple khuyên dùng. iOS, MacOS và watchOS đều sử dụng cấu trúc này làm kiến trúc mặc định để phát triển. Tuy được rất phổ biến và được Apple khuyên dùng nhưng nó cũng có những ưu điểm và nhược điểm, vì vậy bài viết này mình sẽ giới thiệu và giải thích cho các bạn về Cocoa MVC, ưu điểm, nhược điểm và khi nào nên chọn Cocoa MVC sử dụng cho ứng dụng của bạn.
Giải thích về Cocoa MVC
Cocoa MVC là viết tắt của Cocoa Model View Controller. Cocoa MVC gán các đối tượng trong ứng dụng iOS bằng một trong 3 vai trò sau: Model, View hoặc Controller. Mẫu kiến trúc này không chỉ xác định vai trò của các đối tượng trong ứng dụng mà nó còn xác định cả cách các đối tượng giao tiếp với nhau. Ba loại đối tượng này được phân tách khỏi các loại khác bằng các ranh giới trừu tượng và giao tiếp với các đối tượng thuộc các loại khác thông qua các ranh giới đó. Tập hợp các đối tượng của một loại MVC nhất định trong một ứng dụng đôi khi được gọi là một Layer, ví dụ: Layer model.
Cocoa MVC
Model
Những đối tượng được gán với vai trò model trong mẫu kiến trúc Cocoa MVC sẽ làm nhiệm vụ đóng gói dữ liệu cụ thể cho một ứng dụng và xác định logic và tính toán để thao tác và xử lý dữ liệu đó. Những đối tượng này có thể có một hoặc nhiều mối quan hệ với các đối tượng mô hình khác và do đó, đôi khi lớp Model của một ứng dụng thực sự là một hoặc nhiều đối tượng. Phần lớn dữ liệu đều nằm ở Model sau khi nó được tải vào ứng dụng bằng các cách khác nhau(API, Files, …). Vì Model đại diện cho kiến thức và chuyên môn cho một vấn đề cụ thể nên nó có thể được tái sử dụng khi có các trường hợp tương tự. Các Model sẽ không có liên kết trực tiếp với View và View cũng không được trực tiếp sửa dữ liệu của Model mà nó sẽ phải thực hiện thông qua Controller.
Giao tiếp: Các hành động của người dùng trên View sẽ gọi đến Controller, khi này Controller sẽ gọi đến Model tương ứng để thực hiện cập nhật dữ liệu cho Model. Khi Model thay đổi (ví dụ: dữ liệu mới được nhận qua kết nối mạng), nó sẽ thông báo cho Controller, lúc này Controller sẽ cập nhật các Views thích hợp.
View
View là một đối tượng trong ứng dụng mà người dùng có thể nhìn thấy. Một đối tượng View biết cách tự vẽ và có thể phản hồi các hành động của người dùng. Mục đích chính của View là hiển thị dữ liệu từ Model của ứng dụng và cho phép chỉnh sửa dữ liệu đó. Mặc dù vậy, View thường được tách rời khỏi Model trong ứng dụng MVC.
Bởi vì bạn thường sử dụng lại và cấu hình lại chúng, nên View cung cấp tính nhất quán giữa các ứng dụng. Cả UIKit và AppKit framework đều cung cấp các bộ sưu tập View classes và Trình tạo giao diện(Interface Builder) cung cấp hàng tá view objects trong Thư viện của nó.
Giao tiếp: View tìm hiểu về các thay đổi trong dữ liệu của Model thông qua Controller của ứng dụng và truyền đạt các thay đổi do người dùng. ví dụ: văn bản được nhập vào TextField thông qua Controller đến Model của ứng dụng.
Controller
Controller vai trò trung gian giữa một hoặc nhiều View của ứng dụng và một hoặc nhiều Model của nó. Do đó, cController là một đường dẫn mà qua đó View tìm hiểu về những thay đổi trong Model và ngược lại. Controller cũng có thể thực hiện các tác vụ thiết lập và điều phối cho một ứng dụng và quản lý vòng đời của các đối tượng khác.
Giao tiếp: Controller diễn giải các hành động của người dùng được thực hiện trong View và truyền dữ liệu mới hoặc dữ liệu đã thay đổi tới Model. Khi Model thay đổi, Controller sẽ giao tiếp dữ liệu Model mới đó với View để chúng có thể hiển thị nó.
Ưu điểm của Cocoa MVC
Cocoa MVC có khá nhiều ưu điểm như sau:
Dễ hiểu và dễ sử dụng, vì vậy ai cũng có thể làm việc với nó một cách dễ dàng kể cả người mới
Được Apple khuyên dùng, vì vậy nó rất phổ biến khi gặp vấn đề sẽ dễ xử lí.
Giúp developer tách source của họ ra làm các đối tượng khác nhau với 3 vai trò riêng biệt. Khi có lỗi xảy ra chúng ta sẽ khoanh vùng được nơi xảy ra lỗi.
Tránh việc phải tạo một file quá dài, ảnh hưởng tới việc maintain ứng dụng
Có thể tái sử dụng và mở rộng
Nhược điểm của Cocoa MVC
Phân chia nhiệm vụ giữa các vai trò không đồng đều, View chỉ làm nhiệm vụ hiển thị và nhận action từ người dùng, Controller thì chỉ là trung gian điều hướng giữa View-Model, trong khi đó Model phải làm quá nhiều việc từ lưu dữ liệu, xử lí dữ liệu, thực hiện Business Logic của ứng dụng, … Đó là lí do Model còn hay được gọi với cái tên khác là Massive
Không hỗ trợ tốt cho UnitTest bởi View phải phụ thuộc vào cả Controller và Model. View sẽ không thể xử lý được vấn đề gì bởi View không thể nhận yêu cầu và cũng không có dữ liệu để hiển thị. Để tiến hành UnitTest trên View, chúng ta cần giả lập cả Controller và Model.
Đối với các ứng dụng quy mô lớn, quy trình xử lý nghiệp vụ có tính phức tạp cao, lượng dữ liệu lớn thì mô hình MVC trở nên rất cồng kềnh và khó để thực hiện.
Khi nào bạn nên sử dụng Cocoa MVC
Như đã phân tích ở trên, các bạn cũng đã nhìn thấy cách vận hành của mô hình này, các ưu điểm và nhược điểm của nó. Vậy khi nào thì chúng ta nên sử dụng Cocoa MVC cho ứng dụng của mình. Theo mình thì sẽ các tiêu chí như sau:
Khi bạn không biết về các architecture patterns khác tốt hơn, bạn chỉ hiểu rõ về MVC hoặc bạn và member trong team là người mới thì nên chọn Cocoa MVC cho dự án của mình.
Dự án của bạn có kích thước vừa và nhỏ, số iOS developer có số lượng ít, có ít hiểu biết về các architecture patterns khác.
Tổng kết
Qua bài viết trên mình đã giới thiệu cho các bạn về một architecture pattern rất phổ biến trong lập trình ứng dụng iOS. Ngoài ra cũng giúp các bạn hiểu rõ về cách hoạt động cũng như ưu điểm và nhược điểm của Cocoa MVC. Mình hi vọng bài viết này sẽ giúp các bạn có thể hiểu rõ hơn về Cocoa MVC cũng như có những lựa chọn tốt nhất cho từng dự án mà sắp tới các bạn phát triển.
Như mọi người đã biết, struct và enum trong Swift là value types(kiểu giá trị), mặc định thì các thuộc tính của kiểu giá trị thì không thể được sửa đổi ở bên trong các phương thức thể hiện của nó(instance methods).
Tuy nhiên nếu chúng ta cần phải chỉnh sửa các thuộc tính của struct hoặc enum trong một phương thức cụ thể, thì chúng ta sẽ đặt mutating trước các func, nó sẽ giúp các func của bạn có thể thay đổi được các thuộc tính bên trong func và khi kết thúc func giá trị sẽ được nghi lại vào các thuộc tính của struct ban đầu. Phương thức này cũng có thể gán lại một instance mới cho thuộc tính self của nó và nó sẽ được thay thế khi phương thức kết thúc.
Mutating trong Struct
Trong ví dụ này mình sẽ tạo một Struct có tên là Counter và tạo ra một func lấy giá trị của thuộc tính count trong Counter như sau:
struct Counter {
private var count: Int = 0
func getCount() -> Int {
count
}
}
Đây là một ví dụ bình thường về struct, hàm getCount() ở đây không thực hiện thay đổi giá trị của struct Counter mà nó chỉ lấy giá trị của thuộc tính count theo cách thông thường.
Vậy khi chúng ta muốn viết một hàm increase() để tăng giá trị count thông thường chúng ta sẽ viết như sau:
Nếu là class thì sẽ không vấn đề gì vì class là reference type. Ở trường họp này do chúng ta đang viết một func chỉnh sửa thuộc tính count của struct Counter nên xCode sẽ báo lỗi rằng self ở đây là immutable(không thể thay đổi), như đã giải thích ở trên thì struct là value type nên mặc định sẽ không thể thay đổi được thuộc tính của nó trong các func của struct đó.
func getCount() không bị báo lỗi vì func này không làm thay đổi thuộc tính trong struct.
Để func increase() không bị báo lỗi chúng ta cần thêm mutating đằng trước func để xCode biết là func này có thể thay đổi được thuộc tính của struct:
struct Counter {
private var count: Int = 0
func getCount() -> Int {
count
}
mutating func increase() {
count += 1
}
}
var counter = Counter() // count = 0
counter.increase() // count = 1
Để có thể thay đổi được thuộc tính của instance counter thì chúng ta cần phải khai báo nó là var, vì func increase() sẽ thay đổi giá trị của counter vì vậy cần khai báo là var để mutating func có thể gán lại giá trị mới cho instance counter.
Nếu chúng ta để là let Xcode sẽ thông báo lỗi không thể sử dụng mutating func trên giá trị không thể thay đổi, counter đang là một “let” constant. Do struct là value type nên nó là immutable có nghĩa là không thay đổi được, nếu chúng ta cố tình khai báo let count Xcode sẽ thông báo lỗi.
Lỗi khi khai báo let counter để call mutating func
mutating func resetCounter() là một ví dụ, ở trong func này chúng ta thực hiện tạo ra một instance Counter mới với giá trị khởi tạo là 0 và gán lại cho chính instance gọi func này.
Mutating func trong enum
Tương tự như struct, enum cũng là value type và để thay đổi giá trị trong func chúng cũng cần phải sửa dụng mutating cho func đó.
Để hiểu rõ hơn ta đi vào ví dụ sau:
Chúng ta cần tạo ra một công tắc quạt với một tính năng là mỗi khi bấm nút thì sẽ làm thay đổi tốc độ quay của quạt một cách tuần tự và lặp đi lặp lại. Để làm theo yêu cầu chúng ta sẽ tạo enum như sau:
enum FanStateSwitch {
case off, low, high
mutating func next() {
switch self {
case .off:
self = .low
case .low:
self = .high
case .high:
self = .off
}
}
}
var fanSwitch = FanStateSwitch.off
fanSwitch.next() // fanSwitch is low
fanSwitch.next() // fanSwitch is high
fanSwitch.next() // fanSwitch is off
Tương tự như struct khi khởi tạo enum hãy nhớ khởi tạo nó với var thay vì let.
Hi vọng bài viết sẽ giúp các bạn hiểu rõ hơn về mutating func và cách sử dụng, ứng dụng nó vào trong dự án.
Thư viện tiêu chuẩn có một Regex<Output> loại mới.Loại này đại diện cho một biểu thức chính quy mở rộng , cho phép các hoạt động xử lý chuỗi trôi chảy hơn. Bạn có thể tạo một Regex bằng cách khởi tạo từ một chuỗi :
let pattern = "a[bc]+" // matches "a" followed by one or more instances
// of either "b" or "c"
let regex = try! Regex(pattern)
Xcode 14 kích hoạt cú pháp “dấu gạch chéo” của Swift 5.7 cho các ký tự biểu thức chính quy mới được giới thiệu. Trong một số trường hợp, kết quả là mã hiện có sử dụng /làm toán tử không được biên dịch. Bạn có thể định hướng điều này bằng cách thêm dấu ngoặc đơn (/). Bạn cũng có thể vô hiệu hóa hỗ trợ cho cú pháp chữ này bằng cách bỏ chọn “Bật các chữ viết Regex Bare Slash” (SWIFT_ENABLE_BARE_SLASH_REGEX = NO) trong project’s build settings. (93460568)
Các thao tác chuỗi trong phiên bản 5.7 của Thư viện tiêu chuẩn Swift triển khai xác thực cải tiến cho các chỉ mục chuỗi, khắc phục một số trường hợp biên trước đây dẫn đến lỗi thời gian chạy giả. Tuy nhiên, Swift hiện chẩn đoán các nỗ lực sử dụng chỉ mục ngoài giới hạn một cách đáng tin cậy hơn và điều này có thể làm lộ các lỗi lập chỉ mục chưa được phát hiện trước đó khi bạn biên dịch lại mã bằng Xcode 14. Nếu bạn thấy lỗi “Chỉ mục chuỗi nằm ngoài giới hạn” mới sau khi xây dựng lại, mã, sau đó kiểm tra kỹ xem bạn có đang áp dụng chỉ mục cũ cho giá trị chuỗi bị thay đổi không. Trong các bản phát hành trước, các trường hợp như vậy có thể âm thầm dẫn đến các giá trị bị hỏng hoặc vô nghĩa, trong khi ở Swift 5.7, giờ đây chúng gây ra lỗi thời gian chạy một cách đáng tin cậy. (89482809)
Giờ đây, Swift có thể suy ra loại trình giữ chỗ được viết ở nhiều vị trí cấp cao nhất. Loại suy luận hiện được đề xuất trong bản sửa lỗi.
// error: type placeholder may not appear in function return type
func replaceMe() -> _ { // note: replace the placeholder with the inferred type ‘Array<Int>’
[ 42 ]
}
Xcode 13 đã cung cấp cài đặt bản dựng Swift có tên là “Tối ưu hóa thời gian tồn tại của đối tượng” không khả dụng trong Xcode 14. Nếu dự án của bạn đã tùy chỉnh cài đặt bản dựng này, thì giờ đây, nó sẽ trở thành cài đặt do người dùng xác định. Nó không có hiệu lực và bạn có thể loại bỏ nó. Xcode 14 hiện liên tục tối ưu hóa thời gian tồn tại của đối tượng. (91971848)
Xcode 14 giới thiệu một tính năng mới cho khả năng tương tác C: “SE-0324 Thư giãn chẩn đoán cho các đối số con trỏ đối với các hàm C.” Chẩn đoán thoải mái chỉ áp dụng cho các đối số kiểu con trỏ, không phải inout đối số. Giờ đây, chẩn đoán được nới lỏng đối với các inout đối số trong cùng điều kiện như đối số con trỏ. Ví dụ: chuyển đổi này từ một inout Int đối số sang loại tham số C const char * hiện được cho phép:
// C declaration:
// long read_long(const char *input);
func test() -> Int {
var x = 3
return read_long(&x)
}
Trước đây Swift đã chẩn đoán chuyển đổi inout-to-pointer là lỗi:
error: cannot convert value of type 'UnsafePointer<Int>' to expected argument type
'UnsafePointer<CChar>' (aka 'UnsafePointer<Int8>')
return read_long(&x)
^
note: arguments to generic parameter 'Pointee' ('Int' and 'CChar' (aka 'Int8'))
are expected to be equal
return read_long(&x)
^
Swift hiện hỗ trợ các tham chiếu đến optional các phương thức trên siêu dữ liệu giao thức, cũng như các tham chiếu được tra cứu động trên AnyObject siêu dữ liệu. Các tham chiếu này luôn có loại hàm chấp nhận một đối số và trả về một giá trị tùy chọn của loại hàm:
class Object {
@objc func getTag() -> Int
}
@objc protocol P {
@objc optional func didUpdateObject(withTag tag: Int)
}
let getTag: (AnyObject) -> (() -> Int)? = AnyObject.getTag
let didUpdateObject: (any P) -> ((Int) -> Void)? = P.didUpdateObject
Tải dữ liệu từ bộ nhớ thô được biểu thị bằng UnsafeRawPointer, UnsafeRawBufferPointer và các đối tác có thể thay đổi của chúng hiện hỗ trợ truy cập không được phân bổ. Điều này trước đây yêu cầu một cách giải quyết liên quan đến một bản sao trung gian:
let result = unalignedData.withUnsafeBytes { buffer -> UInt32 in
var storage = UInt32.zero
withUnsafeMutableBytes(of: &storage) {
$0.copyBytes(from: buffer.prefix(MemoryLayout<UInt32>.size))
}
return storage
}
Hiện nay
let result = unalignedData.withUnsafeBytes { $0.loadUnaligned(as: UInt32.self) }
Ngoài ra, storeBytes(of:toByteOffset:as:) đã dỡ bỏ hạn chế căn chỉnh, do đó, giờ đây việc lưu trữ thành các phần bù tùy ý của bộ nhớ thô có thể thành công. ( SE-0349 , 93654008 )
UnsafeRawPointer và UnsafeMutableRawPointer có chức năng mới cho số học con trỏ, thêm các chức năng để lấy con trỏ nâng cao tới ranh giới căn chỉnh tiếp theo hoặc trước đó:
```swift
extension UnsafeRawPointer {
public func alignedUp<T>(for: T.type) -> UnsafeRawPointer
public func alignedDown<T>(for: T.type) -> UnsafeRawPointer
public func alignedUp(toMultipleOf alignment: Int) -> UnsafeRawPointer
public func alignedDown(toMultipleOf alignment: Int) -> UnsafeRawPointer
}
```
<!– /wp:code –>
<!– wp:paragraph –>
<p>Bây giờ bạn có thể sử dụng con trỏ tới <code>struct</code> để lấy con trỏ tới một trong các thuộc tính được lưu trữ của nó:</p>
<!– /wp:paragraph –>
<!– wp:code –>
<pre class="wp-block-code"><code>“`swift
withUnsafeMutablePointer(to: &myStruct) {
let interiorPointer = $0.pointer(to: \.myProperty)!
return myCFunction(interiorPointer)
}
“`
Swift đã đơn giản hóa việc so sánh giữa các con trỏ. Vì con trỏ là biểu diễn của các vị trí bộ nhớ trong một nhóm bộ nhớ cơ bản, nên giờ đây Swift cho phép so sánh con trỏ mà không yêu cầu chuyển đổi kiểu với các toán tử ==, !=, <, <=, >và >=. ( SE-0334 , 93667321 )
Giờ đây, bạn có thể sử dụng phương thức withMemoryRebound<T>() này trên bộ nhớ thô, bao gồm UnsafeRawPointer, UnsafeRawBufferPointer và các đối tượng có thể thay đổi của chúng. Ngoài ra, Swift đã làm rõ ngữ nghĩa withMemoryRebound<T>() khi được sử dụng trên bộ nhớ đã nhập (UnsafePointer<Pointee>, UnsafeBufferPointer<Pointee> và các đối tác có thể thay đổi của chúng). Trong khi Swift trước đây yêu cầu Pointee và T có cùng bước tiến, giờ đây bạn có thể liên kết Pointee và T lại trong các trường hợp là tổng hợp hoặc ngược lại. Ví dụ: được cung cấp một UnsafeMutableBufferPointer<CGPoint>, bây giờ bạn có thể sử dụng withMemoryRebound để hoạt động tạm thời trên một UnsafeMutableBufferPointer<CGFloat>, bởi vì CGPoint là một tập hợp của CGFloat. ( SE- 0333 , 93668889 )
Giờ đây, bạn có thể gọi một hàm chung có giá trị là loại giao thức ở những nơi trước đây không thành công vì any các loại không tuân theo giao thức của chúng. Ví dụ:
protocol P {
associatedtype A
func getA() -> A
}
func takeP<T: P>(_ value: T) { }
func test(p: any P) {
takeP(p) // was an error "type 'any P' cannot conform to 'P'", now accepted
}
Điều này hoạt động bằng cách mở giá trị của loại giao thức và chuyển loại cơ bản trực tiếp đến chức năng chung. ( SE-0352 , 93669112 )
Bây giờ, bạn có thể sử dụng một biểu thức giá trị mặc định với một loại tham số chung để làm mặc định đối số và loại của nó:
Trình biên dịch hiện chấp nhận một cuộc gọi đến compute()và [Int] được suy ra C tại các trang web cuộc gọi không cung cấp đối số một cách rõ ràng. ( SE-0347 , 93669409 )
Giờ đây, bạn có thể sử dụng các giao thức với các loại và Self yêu cầu được liên kết dưới dạng các loại giá trị với từ khoá any. Bạn có thể gọi các phương thức giao thức trả về các loại được liên kết trên một kiểu any; kết quả là loại bị xóa đến giới hạn trên của loại được liên kết, là một kiểu any khác có cùng ràng buộc với loại được liên kết. Ví dụ:
protocol Surface {...}
protocol Solid {
associatedtype SurfaceType: Surface
func boundary() -> SurfaceType
}
let solid: any Solid = ...
// Type of 'boundary' is 'any Surface'
let boundary = solid.boundary()
Bạn không thể sử dụng các phương thức giao thức có loại được liên kết hoặc Self với any; tuy nhiên, cùng với SE-0352 , bạn có thể chuyển kiểu any cho một hàm có tham số chung bị ràng buộc với giao thức. Trong ngữ cảnh chung, các mối quan hệ kiểu là rõ ràng và bạn có thể sử dụng tất cả các phương thức giao thức. ( SE-0309 , 93928911)
Giờ đây, các giao thức có thể khai báo danh sách một hoặc nhiều loại được liên kết chính , cho phép viết các yêu cầu cùng loại trên các loại được liên kết đó bằng cách sử dụng cú pháp dấu ngoặc nhọn:
Giờ đây, bạn có thể viết tên giao thức theo sau là các đối số loại trong dấu ngoặc nhọn, chẳng hạn như Graph<Int, String>, ở bất kỳ đâu mà yêu cầu tuân thủ giao thức có thể xuất hiện:
func shortestPath<V, E>(_: some Graph<V, E>, from: V, to: V) -> [E]
extension Graph<Int, String> {...}
func build() -> some Graph<Int, String> {}
Tên giao thức theo sau bởi dấu ngoặc nhọn là cách viết tắt của yêu cầu tuân thủ, cùng với yêu cầu cùng loại đối với các loại liên kết chính của giao thức. Hai ví dụ đầu tiên ở trên tương đương như sau:
func shortestPath<V, E, G>(_: G, from: V, to: V) -> [E]
where G: Graph, G.Vertex == V, G.Edge == E
extension Graph where Vertex == Int, Edge == String {...}
Hàm build()trả về some Graph<Int, String> không thể được viết bằng một mệnh đề where; đây là một ví dụ về loại kết quả không rõ ràng bị ràng buộc, đây là biểu thức mới trong Swift 5.7. ( SE-0346 , 93929372 )
Bạn có thể sử dụng các giao thức áp dụng các loại được liên kết chính với any để bật các loại tồn tại bị ràng buộc. Ví dụ:
let strings: any Collection<String> = [ "Hello" ]
Điều này làm cho việc viết các trình bao bọc xóa kiểu cho mã chung đơn giản hơn nhiều vì một loại trình bao bọc riêng biệt không còn cần thiết nữa:
protocol Producer<T> {
associatedtype T
func produce() -> T
}
typealias AnyProducer<T> = any Producer<T>
/*
struct AnyProducer<T> {
var wrappedProduce: () -> T
}
*/
Giờ đây, bạn có thể sử dụng các giao thức với các loại được liên kết chính trong các loại tồn tại, cho phép các ràng buộc cùng loại đối với các loại được liên kết đó.
let strings: any Collection<String> = [ "Hello" ]
Lưu ý rằng các tính năng ngôn ngữ yêu cầu hỗ trợ thời gian chạy như diễn viên động ( is, as?, as!), cũng như cách sử dụng chung của các tồn tại được tham số hóa trong các loại chung (ví dụ: Array<any Collection<Int>>) liên quan đến việc kiểm tra tính khả dụng bổ sung để sử dụng. Giờ đây, bạn có thể triển khai lại các tập quán ở các vị trí chung với cấu trúc trình bao bọc xóa kiểu chung, giờ đây việc triển khai đơn giản hơn nhiều:
struct AnyCollection<T> {
var wrapped: any Collection<T>
}
let arrayOfCollections: [AnyCollection<T>] = [ /**/ ]
Giờ đây, bạn có thể sử dụng các loại không rõ ràng trong tham số của hàm và chỉ số con khi chúng cung cấp cú pháp tốc ký để giới thiệu tham số chung. Ví dụ như sau:
func horizontal(_ v1: some View, _ v2: some View) -> some View {
HStack {
v1
v2
}
}
Với điều này, some trong một loại tham số cung cấp một khái quát hóa trong đó người gọi chọn loại tham số cũng như giá trị của nó, trong khi some ở loại kết quả cung cấp một khái quát hóa trong đó người gọi chọn loại và giá trị kết quả. ( SE- 0341 , 93675336 )
Giờ đây, bạn có thể suy ra các loại tham số và kết quả từ phần thân của một bao đóng đa câu lệnh. Không còn sự phân biệt giữa các lần đóng đơn và đa câu lệnh.
Việc sử dụng các bao đóng trở nên ít cồng kềnh hơn bằng cách loại bỏ nhu cầu liên tục chỉ định các loại bao đóng rõ ràng, đôi khi có thể khá lớn (ví dụ: khi có nhiều tham số hoặc loại kết quả bộ dữ liệu phức tạp).
Ví dụ:
func map<T>(fn: (Int) -> T) -> T {
return fn(42)
}
func computeResult<U: BinaryInteger>(_: U) -> U { /* processing */ }
let _ = map {
if let $0 < 0 {
// Do some processing.
}
return computeResult($0)
}
Giờ đây, bạn có thể suy ra loại kết quả map từ phần thân của dấu đóng được truyền dưới dạng đối số. ( SE-0326 , 93669647 )
Giờ đây, bạn có thể hủy bao bọc các biến tùy chọn bằng cú pháp tốc ký làm ẩn phần khai báo hiện có. Ví dụ như sau:
let foo: String? = "hello world"
if let foo {
print(foo) // Prints "hello world”.
}
tương đương với:
let foo: String? = "hello world"
if let foo = foo {
print(foo) // Prints "hello world”.
}
Giờ đây, bạn có thể làm cho các khai báo không khả dụng để sử dụng trong ngữ cảnh không đồng bộ với thuộc tính @available(*, noasync). Điều này bảo vệ người dùng API chống lại hành vi không xác định có thể xảy ra khi API sử dụng hoặc khuyến khích sử dụng lưu trữ cục bộ theo luồng trên các điểm tạm ngưng. Nó cũng bảo vệ các nhà phát triển chống lại việc giữ khóa trên các điểm treo, điều này có thể dẫn đến hành vi không xác định, đảo ngược mức độ ưu tiên hoặc deadlocks. ( SE-0340 , 93673989 )
Tập lệnh cấp cao nhất hiện hỗ trợ cuộc gọi không đồng bộ. Sử dụng một await bằng cách gọi một hàm không đồng bộ hoặc truy cập một biến bị cô lập sẽ chuyển cấp cao nhất sang ngữ cảnh không đồng bộ. Là một bối cảnh không đồng bộ, các biến cấp cao nhất được cách ly và cấp cao nhất được chạy trên .@MainActor@MainActor Lưu ý rằng quá trình chuyển đổi ảnh hưởng đến độ phân giải quá tải chức năng và bắt đầu một vòng lặp chạy ẩn để điều khiển máy móc đồng thời. Các tập lệnh chưa sửa đổi không bị ảnh hưởng bởi thay đổi này trừ khi -warn-concurrency được chuyển đến lời gọi trình biên dịch. Với -warn-concurrency, các biến ở cấp cao nhất được tách biệt với tác nhân chính và bối cảnh cấp cao nhất được tách biệt với tác nhân chính nhưng không phải là ngữ cảnh không đồng bộ. ( SE-0343 , 93674157 )
Swift hiện hỗ trợ lập trình phân tán với việc giới thiệu các tác nhân phân tán. Để biết thêm thông tin, hãy xem SE-0336 , SE-0344 . (70840120)
Bây giờ bạn có thể khai báo distributed actor và distributed func bên trong tệp distributed actor. Các tác nhân phân tán cung cấp các đảm bảo cách ly mạnh hơn so với các tác nhân “cục bộ” và chúng cho phép thực hiện các kiểm tra bổ sung đối với các loại trả về và tham số của các phương thức phân tán, ví dụ: kiểm tra xem chúng có tuân thủ Codable. Bạn có thể gọi các phương thức phân tán trên các tham chiếu “từ xa” của các tác nhân phân tán, biến các lệnh gọi đó thành các lệnh gọi thủ tục từ xa, bằng cách triển khai hệ thống tác nhân phân tán có thể cắm và mở rộng cho người dùng. Bản thân Swift không cung cấp bất kỳ hệ thống tác nhân phân tán cụ thể nào; tuy nhiên, các gói trong hệ sinh thái hoàn thành vai trò cung cấp các triển khai đó.
distributed actor Greeter {
var greetingsSent = 0
distributed func greet(name: String) -> String {
greetingsSent += 1
return "Hello, \(name)!"
}
}
func talkTo(greeter: Greeter) async throws {
// The isolation of distributed actors is stronger. You can't refer to
// any stored properties of distributed actors from outside of them.
greeter.greetingsSent // You can't access the distributed actor-isolated property 'name' from a non-isolated context.
// Remote calls are implicitly throwing and async,
// to account for the potential networking involved.
let greeting = try await greeter.greet(name: "Alice")
print(greeting) // Hello, Alice!
}
Trình khử khởi tạo, hầu hết các trình khởi tạo cho actor các loại và các loại bị hạn chế bởi một tác nhân toàn cầu như @MainActor các quy tắc đã sửa đổi về những biểu thức nào được phép trong phần thân của chúng. Là một phần của SE-327, mục tiêu của các bản sửa đổi này là cải thiện tính an toàn và biểu cảm của ngôn ngữ. Nhiều mẫu lập trình khác hiện được cho phép trong các bộ khởi tạo này.
Ví dụ: trình khởi tạo không đồng bộ của phiên bản actor trước Swift 5.7 đã đưa ra chẩn đoán bất kỳ lúc nào self thoát khỏi trình khởi tạo trước khi quay lại. Mục đích của chẩn đoán đó là để bảo vệ chống lại một cuộc chạy đua dữ liệu có thể xảy ra khi truy cập các thuộc tính được lưu trữ riêng biệt, nhưng nó đã được phát ra ngay cả khi không có quyền truy cập nguy hiểm.
Trong Swift 5.7, trình biên dịch hiện kiểm tra các trình khởi tạo này để tìm quyền truy cập nguy hiểm vào các thuộc tính được lưu trữ bị cô lập xảy ra sau khi thoát self:
actor Database {
// ... other properties ...
var rows: Int = 0
init(_ world: DataUser) {
defer {
print("last = \(self.rows)") // ❌ This access to 'rows' is illegal.
}
print("before = \(self.rows)") // ✅ This access to 'rows' is OK.
world.publishDatabase(self) // ✅ Passing 'self' is OK in Swift 5.7+.
print("after = \(self.rows)") // ❌ This access to 'rows' is illegal.
Task { [weak self] in // ✅ Capturing 'self' is OK in Swift 5.7+.
while let db = self { await db.prune() }
}
}
}
Đây là kiểm tra nhạy cảm với luồng điều khiển, nghĩa là truy cập bất hợp pháp không nhất thiết phải xuất hiện trên dòng nguồn sau khi thoát self(trong ví dụ trên, xem xét thời điểm defer thực thi). Trình biên dịch luôn chỉ ra một trong những lối thoát self khiến quyền truy cập trở thành bất hợp pháp.
Ngoài ra, các công cụ khởi tạo ủy quyền của một actor không còn luôn luôn không bị cô lập. Điều này có nghĩa là trình async khởi tạo ủy quyền có thể thực hiện những việc tương tự như trình khởi tạo không ủy quyền. (84476555)
Trình khởi tạo actor không còn yêu cầu viết từ khóa convenience để ủy quyền (SE-327). Trước Swift 5.7, việc thêm hoặc xóa convenience đối với public init của một diễn viên là một thay đổi không linh hoạt, đối với các thư viện được biên dịch có bật tiến hóa. Các thư viện được biên dịch cho Swift 5.7+ hiện có khả năng phục hồi trước những thay đổi trong quá trình triển khai các trình khởi tạo đó để ủy quyền hay không và các chương trình hiện có được biên dịch cho Swift 5.7+ sẽ không yêu cầu biên dịch lại. (87567878)
Các loại mới đại diện cho thời gian và đồng hồ hiện đã có sẵn. Điều này bao gồm một giao thức Clock để xác định đồng hồ, cho phép bạn xác định khái niệm về “hiện tại” và cách đánh thức sau một khoảng thời gian nhất định. Một giao thức InstantProtocol mới để xác định thời gian cũng có sẵn. Và một giao thức DurationProtocol mới để xác định khoảng thời gian đã trôi qua giữa hai loại InstantProtocol nhất định cũng có sẵn. Các loại để sử dụng chung là phổ biến nhất ClockSuspending và ClockContinuous, đại diện cho các đồng hồ cơ bản nhất cho hệ thống. Loại ClockSuspendingClock không tiến triển khi máy bị treo, ngược lại ContinuousClock tiến triển bất kể trạng thái của máy.
Clock cũng có các phương pháp để đo thời gian thực hiện công việc đã trôi qua. Trong trường hợp của SuspendingClock và ContinuousClock điều này đo lường với độ phân giải cao và phù hợp với điểm chuẩn.
let clock = ContinuousClock()
let elapsed = clock.measure {
someLongRunningWork()
}
Trình biên dịch hiện phát ra cảnh báo khi một lớp không phải là lớp cuối cùng tuân theo một giao thức áp đặt yêu cầu cùng loại giữa Selfvà một loại được liên kết. Loại yêu cầu này làm cho sự phù hợp không hợp lý cho các lớp con.
Ví dụ: Swift 5.6 cho phép đoạn mã sau, trong thời gian chạy mã này sẽ xây dựng một phiên bản C và sub C không như mong đợi:
protocol P {
associatedtype A : Q where Self == Self.A.B
}
protocol Q {
associatedtype B
static func getB() -> B
}
class C : P {
typealias A = D
}
class D : Q {
typealias B = C
static func getB() -> C { return C() }
}
extension P {
static func getAB() -> Self {
// This is well-typed because `Self.A.getB()` returns
// `Self.A.B`, which is equivalent to `Self`.
return Self.A.getB()
}
}
class SubC : C {}
// P.getAB() declares a return type of `Self`, so it should
// return `SubC`, but it actually returns a `C`.
print(SubC.getAB())
Để làm cho ví dụ trên đúng, class C cần phải trở thành final(trong trường hợp đó SubC không thể được khai báo) hoặc giao thức P cần được thiết kế lại để không bao gồm yêu cầu cùng loại Self == Self.A.B. (93675134)
Trình biên dịch hiện đưa ra chính xác các cảnh báo cho nhiều biểu thức hơn khi sử dụng tuân thủ giao thức và có thể không khả dụng khi chạy. Trước đây, các biểu thức tham chiếu thành viên và biểu thức xóa loại sử dụng các tuân thủ có khả năng không khả dụng chưa được chẩn đoán, dẫn đến các sự cố có thể xảy ra trong thời gian chạy.
struct Pancake {}
protocol Food {}
extension Food {
var isGlutenFree: Bool { false }
}
@available(macOS 12.0, *)
extension Pancake: Food {}
@available(macOS 11.0, *)
func eatPancake(_ pancake: Pancake) {
if (pancake.isGlutenFree) { // Warning: Conformance of 'Pancake' to 'Food' is only available in macOS 12.0 or newer.
eatFood(pancake) // Warning: Conformance of 'Pancake' to 'Food' is only available in macOS 12.0 or newer.
}
}
func eatFood(_ food: Food) {}
Các loại mờ (được biểu thị bằng some) hiện có thể được sử dụng ở các vị trí cấu trúc trong một loại kết quả, bao gồm cả việc có nhiều loại mờ trong cùng một kết quả. Ví dụ:
Các giao thức khác nhau trong thư viện chuẩn hiện khai báo các loại liên kết chính, ví dụ Sequencevà Collectionkhai báo một loại liên kết chính duy nhất Element. Ví dụ: điều này cho phép ghi lại các loại some Collection<Int>và any Collection<Int>. ( SE-0358 , 93929895 )
Vấn đề đã giải quyết
Các thuộc tính được lưu trữ trong Swift không được có thông tin loại có khả năng không khả dụng trong thời gian chạy. Tuy nhiên, trước Swift 5.7, trình biên dịch đã chấp nhận lỗi thuộc tính @available trên thuộc tính được lưu trữ khi thuộc tính có công cụ lazy sửa đổi hoặc trình bao bọc thuộc tính được đính kèm. Điều này có thể dẫn đến sự cố cho các ứng dụng chạy trên hệ điều hành cũ hơn. Trình biên dịch Swift hiện từ chối @available trên tất cả các thuộc tính được lưu trữ. (82713248) (FB9594187)
Phiên bản không đồng bộ addTeardownBlock của phương pháp trong XCTestCase hiện đã có. (85453819) (FB9762503)
Chẩn đoán về các biểu thức giá trị mặc định không tách biệt được giới thiệu cho Swift 5.6 trong bản phát hành Xcode 13.3 không còn nữa. Quy tắc được đề xuất trong SE-0327 không đủ chính xác để tránh gắn cờ một mẫu phổ biến nhưng vô thưởng vô phạt trong mã SwiftUI liên quan đến các thuộc tính @StateObject và @MainActor. (88971160)
Quá trình tái cấu trúc “Tạo Trình khởi tạo Memberwise” thêm chính xác các tham số cho các biến được đánh dấu bằng trình bao bọc thuộc tính. (89057767) (FB9910083)
Các thao tác chuỗi trong phiên bản 5.7 của Swift Standard Library triển khai xác thực cải tiến cho các chỉ mục chuỗi, khắc phục một số trường hợp biên trước đây dẫn đến lỗi thời gian chạy giả. Tuy nhiên, Swift hiện chẩn đoán các nỗ lực sử dụng chỉ mục ngoài giới hạn một cách đáng tin cậy hơn và điều này có thể làm lộ các lỗi lập chỉ mục chưa được phát hiện trước đó khi bạn biên dịch lại mã bằng Xcode 14. Nếu bạn thấy lỗi “Chỉ mục chuỗi nằm ngoài giới hạn” mới sau khi xây dựng lại, mã, sau đó kiểm tra kỹ xem bạn có đang áp dụng chỉ mục cũ cho giá trị chuỗi bị thay đổi không. Trong các bản phát hành trước, các trường hợp như vậy có thể âm thầm dẫn đến các giá trị bị hỏng hoặc vô nghĩa, trong khi ở Swift 5.7, giờ đây chúng gây ra lỗi thời gian chạy một cách đáng tin cậy. (89482809)
Khi xây dựng các thể hiện Chuỗi từ các chuỗi C, giờ đây Swift thực thi nghiêm ngặt việc chấm dứt null của bộ đệm đầu vào nếu bạn chuyển một đối số bằng cách chuyển đổi con trỏ. Hơn nữa, inout-to-pointer chuyển đổi hiện không được dùng cho cấu trúc Chuỗi. Các chức năng bị ảnh hưởng là String.init(cString:), String.init?(validatingUTF8:), String.decodeCString(_:as:repairingInvalidCodeUnits:) và String.init(decodingCString:as:). (90336023)
Chỉnh sửa tất cả trong Phạm vi đổi tên chính xác tất cả các lần xuất hiện của các biến được ghi lại bằng cách sử dụng cú pháp chụp tốc ký bao đóng [capturedVariable] hoặc cú pháp tốc ký if let optionalVariable. (91311033)
Bạn có thể sử dụng các giao thức áp dụng các loại được liên kết chính với từ khóa any để bật các loại tồn tại bị ràng buộc.Ví dụ:
let strings: any Collection<String> = [ "Hello" ]
Điều này làm cho việc viết các trình bao bọc xóa kiểu cho mã chung đơn giản hơn nhiều vì một loại trình bao bọc riêng biệt không còn cần thiết nữa:
protocol Producer<T> {
associatedtype T
func produce() -> T
}
typealias AnyProducer<T> = any Producer<T>
/*
struct AnyProducer<T> {
var wrappedProduce: () -> T
}
*/
Trình biên dịch Swift không còn cảnh báo về các yêu cầu dư thừa trong các khai báo chung. Ví dụ: trong phiên bản beta 1, đoạn mã sau đã chẩn đoán một cảnh báo về T.Iterator : IteratorProtocol yêu cầu là dư thừa, bởi vì nó được ngụ ý bởi T : Sequence:
func firstElement<T: Sequence>(_: T) -> T.Element where T.Iterator: IteratorProtocol {...}
Một yêu cầu dư thừa không chỉ ra lỗi mã hóa và đôi khi nên đánh vần chúng cho mục đích tài liệu. Vì lý do này, những cảnh báo này hiện mặc định bị tắt.
Bạn có thể nhận hành vi trước đó và bật lại các cảnh báo này bằng cách đặt OTHER_SWIFT_FLAGS-X tùy chọn bản dựng trong Xcode thành “ frontend -warn-redundant-requirements”. (92092635)
Các phép chuyển động ( is, as!, as?) đến và từ các loại tồn tại được tham số hóa hiện kiểm tra chính xác các ràng buộc đối với các loại này. (92197049)
Đã sửa lỗi: Một số câu lệnh switch phức tạp nhất định trong phần đóng nhiều câu lệnh có thể khiến trình biên dịch gặp sự cố nếu chúng chứa câu lệnh fallthrough yêu cầu suy luận kiểu tại đích của nó, ví dụ: đích case sử dụng khớp mẫu với let các liên kết như case (let ..., let ...). (93796211)
Swift không thực hiện kiểm tra Có thể gửi khi thoát khỏi một actor để gọi vào mã không đồng bộ, không bị cô lập. Ví dụ:
func f(_: NS) async { }
actor A {
func g(_ ns: NS) async {
await f(ns) // Warn about passing non-Sendable type 'NS' to a non-isolated async function.
}
}
Vấn đề đã biết
#if canImport(AudioVideoBridging)không biên dịch trên Mac Catalyst. (89289575) Giải pháp thay thế : Sử dụng #if os(macOS) để hạn chế AudioVideoBridging code cho các nền tảng được hỗ trợ.
~= và các trường hợp trong switches thành công nếu toàn bộ chuỗi khớp, thay vì nếu có một kết quả khớp bên trong chuỗi. Điều này có thể thay đổi tùy thuộc vào kết quả của Swift Evolution cho SE-0357 . (93918632)
Trong Swift, một số kiểu chữ Foundation cho các loại chức năng (chẳng hạn như NSItemProvider.CompletionHandler) sẽ thấy @Sendable các loại chức năng, điều này có thể dẫn đến các cảnh báo không mong muốn trong mã chưa áp dụng Đồng thời Swift. Để loại bỏ các cảnh báo, hãy thay thế một tham chiếu đến bí danh kiểu chữ trong mã nguồn Swift bằng kiểu bên dưới của nó mà không có phần mở rộng @Sendable. (98343624)
Ví dụ: thay thế: var completion: NSItemProvider.CompletionHandler bằng: var completion: (NSSecureCoding?, Error?) -> Void
Bài viết được dịch dựa trên nội dung release note xCode14 của Apple Refer: https://developer.apple.com/documentation/xcode-release-notes/xcode-14-release-notes
UISplitViewController hiện hỗ trợ sidebars trong các ứng dụng Mac được xây dựng bằng Mac Catalyst. Để bật sidebars, hãy đặt Primary Style trong trình kiểm tra thuộc tính của bộ điều khiển chế độ split view. (82004740)
Trình tạo giao diện hiện hỗ trợ các nhóm mục trung tâm mới trên UINavigationItem. (83252931)
Đã thêm một checkbox trong Attributes inspector để bật giao diện người dùng tìm và thay thế UI cho UITextView. (83726669)
Trình tạo giao diện hiện cập nhật các cảnh không đồng bộ. (83786577)
Trình tạo giao diện hiện hỗ trợ tác giả NSComboButton. (85583290)
Trình tạo giao diện hiện hỗ trợ tác giả với MKMapView, MKMapConfiguration including standard, imagery và hybrid. (85607049)
Chuyển bàn phím từ trình kiểm tra thuộc tính của UIViewController để hiểu cách bàn phím ảnh hưởng đến hướng dẫn bố cục trong canvas. (87975498)
Một hộp kiểm mới xuất hiện để bật giao diện người dùng tìm và thay thế tiêu chuẩn. (88049266)
Giờ đây, bạn có thể chỉnh sửa cấu hình mặc định của Biểu tượng SF (bao gồm phông chữ, tỷ lệ và trọng lượng) bằng NSButton và NSImageView bằng cách chọn biểu tượng thông qua trình kiểm tra hình ảnh của điều khiển. (88400241)
Hỗ trợ cho iOS để cho phép dán nội dung bằng một lần nhấn mà không cần thông báo hoặc cảnh báo dán. Điều khiển có thể nhắm mục tiêu bất kỳ đối tượng nào tuân theo UIPasteConfigurationSupporting (ví dụ: UIResponder) để nhận nội dung đã dán. (88648426)
Truy cập và tìm kiếm Ký hiệu SF thông qua tab thư viện ký hiệu. Mở thư viện (Xcode > View > Show Library) và nhấp vào tab Symbols. Bạn có thể kéo các biểu tượng vào source editor. (88726368)
Trình tạo giao diện bao gồm NSColorWell có các kiểu mặc định, tối thiểu và mở rộng mới trong macOS 13. (89051231)
Trình tạo giao diện hiện hỗ trợ tác giả MKPointOfInterestFilter. (89368386)
Trình tạo giao diện hiện hỗ trợ tác giả MKLookAroundViewController. (90994596)
Trình tạo giao diện hiện hỗ trợ tác giả RoomCaptureView. (91640003)
Vấn đề đã giải quyết
UIBarButtonItemGroup đã được thêm vào thư viện đối tượng Interface builder. Nó có thể được kéo vào một UINavigationItem để cung cấp các Center Items. (19160962)
UIColorWell hiện có sẵn trong thư viện đối tượng. Khi chạm vào, điều khiển sẽ hiển thị bộ chọn màu. (67016855)
Giờ đây, bạn có thể bật hướng dẫn bố cục bàn phím trên cảnh UIView thông qua trình kiểm tra kích thước. Hạn chế chế độ xem đối với hướng dẫn bố cục để chúng điều chỉnh trong quá trình bố trí khi bàn phím hiển thị trên màn hình. (81959069) (FB9514618)
Tạo khả năng hiển thị phác thảo cho các tài liệu mới phù hợp với trạng thái được chuyển đổi của người dùng cuối cùng. (82857926) (FB9607879)
Đã khắc phục sự cố với kết nối ổ cắm và hành động với AppDelegates dựa trên AppleScript. (83373726) (FB9643535)
Đã khắc phục sự cố khi lưu tài liệu có nhiều Trình điều khiển Chế độ xem Nhìn xung quanh sẽ làm Xcode bị lỗi. (92304543)
Bảng phân cảnh WatchKit không được dùng trong watchOS 7.0 trở lên. Vui lòng chuyển sang SwiftUI và Vòng đời SwiftUI. (94058186)
Chế độ xem phác thảo của Interface builder hiện lưu và khôi phục trạng thái hiển thị/độ rộng trên toàn cầu thay vì trên mỗi tài liệu. (97084370)
Vấn đề đã biết
Kích thước phông chữ không thay đổi để phù hợp với kích thước kiểm soát cho NSComboButton. (94610724) (FB10094813)
Linking
Vấn đề đã biết
Các ứng dụng Swift được xây dựng bằng Xcode 14 có thể không liên kết được với các tệp libswiftFoundation.dylib. Điều này có thể khiến ứng dụng hoạt động sai khi chạy trên các hệ điều hành trước macOS 13 Ventura và iOS 16, bao gồm cả việc in các chuỗi không chính xác và đưa ra các ngoại lệ đối với các phương thức bị thiếu trên các loại dữ liệu của Tổ chức. (99457165) Giải pháp thay thế : Tham chiếu rõ ràng một ký hiệu từ trong mã libswiftFoundation, ví dụ bằng cách thêm vào một hàm _ = JSONDecoder()
Localization
Các tính năng mới
Bây giờ bạn có thể xuất Swift Packages cục bộ để bản địa hóa. Xcode tạo một danh mục bản địa hóa duy nhất cho tất cả các dự án và Swift Packages có trong một project hoặc workspace. Bạn cũng có thể sử dụng xcodebuild -importLocalizations và xcodebuild -exportLocalizations để export hoặc import gói Swift. (56355281)
Vấn đề đã giải quyết
Các chuỗi được bản địa hóa chỉ định rõ ràng một bảng tham số không phải là chuỗi ký tự không còn được trích xuất khi chạy genstrings hoặc xuất để bản địa hóa. Trước đây, genstrings mặc định là một bảng có tên Localizable.strings. (65063595)
Đã khắc phục sự cố trong đó Xcode đôi khi không phát sinh lỗi khi xuất .strings tệp không đúng định dạng để bản địa hóa. (85278818)
Đã khắc phục sự cố trong đó Xcode âm thầm bỏ qua XLIFF không đúng định dạng khi nhập bản địa hóa. (86849358) (FB9819403)
xcodebuild -importLocalizations và xcodebuild -exportLocalizations hiện bao gồm dấu thời gian cho các hoạt động bản địa hóa. (89373526)
Xcode tự động trích xuất NSHumanReadableDescription từ các tệp Info.plist khi xuất để bản địa hóa. (89591666) (FB9935770)
Các khóa trong .strings tệp tồn tại trong một .stringsdict với NSStringDeviceSpecificRuleType không còn được đánh dấu là translate=”no” trong XLIFF đã xuất, bởi vì những khóa này cũng rơi trở lại giá trị tệp .strings khi chạy. (90785024)
Đã khắc phục sự cố trong đó Xcode đưa ra cảnh báo đối với các đơn vị chưa được dịch được đánh dấu là translate="no"khi nhập bản địa hóa. (91692843) (FB9982115)
Metal
Các tính năng mới
TextureConverter 2.0 thêm hỗ trợ giải nén kết cấu, số liệu lỗi kết cấu nâng cao và hỗ trợ đọc và ghi tệp KTX2.Thư viện AppleTextureConverter mới giúp TextureConverter có sẵn để tích hợp vào các công cụ và công cụ của bên thứ ba. (82244472)
Vấn đề đã biết
Hồ sơ Chụp kim loại có chứa pipeline bị vô hiệu hóa. (93255574)
Organizer
Các tính năng mới
Phản hồi ảnh chụp màn hình TestFlight hiện có sẵn trong Xcode Organizer. Giờ đây, bạn có thể xem ảnh chụp màn hình và phản hồi bằng văn bản cho iOS và macOS trong Xcode bằng cách chọn mục “Phản hồi” trong phần báo cáo của thanh bên. Để bắt đầu, hãy đăng nhập bằng tài khoản Nhà phát triển Apple được liên kết với ứng dụng TestFlight của bạn và mở Trình tổ chức bằng cách chọn Cửa sổ > Trình tổ chức trong thanh menu. Ngoài việc xem phản hồi, bạn có thể liên hệ trực tiếp với người thử nghiệm và chia sẻ phản hồi với các thành viên trong nhóm phát triển của mình. (56519107)
TestFlight Screenshot Feedback hiện có thể được mở trong Xcode Organizer thông qua nút “Open in Xcode 14” trong App Store Connect. (83599827)
Previews
Các tính năng mới
Bản xem trước Xcode hiện tự động tiếp tục khi tạo dự án mới. (50474683)
Xem trước Xcode không còn tạm dừng sau khi chỉnh sửa tệp và chuyển về chế độ xem trước và tiếp tục hoặc khi các dự án sửa đổi thư mục nguồn trong quá trình xây dựng. (71593736)
Lỗi từ các bản xem trước tiện ích và phức tạp hiện được hiển thị trong khung vẽ. (76966327)
Bản xem trước Xcode hiện sử dụng dữ liệu từ các tệp Cấu hình StoreKit nếu một tệp được đặt trong các tùy chọn chạy của lược đồ. Bản xem trước chỉ hỗ trợ một tập hợp con API StoreKit. (82312384)
Chẩn đoán xem trước hiện có thể truy cập được bằng mục trình đơn Trình chỉnh sửa > Canvas > Chẩn đoán của Xcode. (92620156)
Vấn đề đã giải quyết
Bản xem trước Xcode hiện hiển thị chính xác các chuỗi ký tự có chứa chuỗi thoát unicode Swift. (32722474)
Bản xem trước Xcode hiện có thể chạy trên các thiết bị vật lý mà không yêu cầu ứng dụng chứa, giúp dễ dàng xem trước trên thiết bị cho các khung và gói Swift. Xcode tự động chuẩn bị một ứng dụng đã ký phù hợp cho danh tính ký mặc định của bạn để lưu trữ bản xem trước. (50206641)
Khung Xcode Previews hiện hỗ trợ và mặc định là zoom-to-fit. (51146527)
Đã khắc phục một số sự cố khi sử dụng bản xem trước watchOS trong Xcode. Các bản xem trước hiện hoạt động chính xác khi điều hướng giữa các tệp trong ứng dụng iOS chứa hoặc ứng dụng watchOS được nhúng hoặc các khung và gói được bao gồm bởi một hoặc nhiều mục tiêu đó. (53183015)
Đã sửa một số trường hợp khi thực hiện chỉnh sửa theo một thứ tự cụ thể sẽ khiến các bản xem trước không thành công với lỗi thiếu biểu tượng (53740398)
Bản xem trước Xcode hiện có thể sử dụng chương trình có thể chạy được khi quyết định sử dụng ứng dụng nào để lưu trữ bản xem trước. Ví dụ: trong dự án có khung mà cả phiên bản đầy đủ và phiên bản beta của ứng dụng đều dùng chung, Xcode Previews tự động chọn ứng dụng để khởi chạy để xem trước dựa trên lựa chọn trong lược đồ. (60251198)
Đã khắc phục sự cố khi sử dụng nhiều bản xem trước sẽ gây ra lỗi hết thời gian chờ trong Bản xem trước Xcode. (68939151)
Đã khắc phục sự cố khiến Bản xem trước Xcode không thành công đối với các tệp có hai dấu ngoặc nhọn đóng trên cùng một dòng. (74035344) (FB8992136)
Xem trước Xcode không còn tạm dừng trên các chỉnh sửa lớn. Thay vào đó, Bản xem trước tiếp tục tự động xây dựng bằng cách phát hiện các loại thay đổi được thực hiện và tự động điều chỉnh tần suất cập nhật để cân bằng thời lượng pin và độ trễ. (77799105)
Đã khắc phục sự cố trong Bản xem trước Xcode gây ra tình trạng tạm dừng thường xuyên khi sử dụng các dự án Swift Playgrounds được mở trong Xcode. (79206975)
Đã khắc phục sự cố trong một số dự án mà lỗi tải gói không bao giờ biến mất trong Bản xem trước Xcode. (79207076)
Đã khắc phục sự cố trong đó Bản xem trước Xcode có thể không hiển thị chính xác sau khi đóng và mở canvas. (83789694)
Đã sửa các trường hợp Bản xem trước Xcode không phản ánh các thay đổi được thực hiện đối với các tệp trong Gói Swift. (84529522)
Đã khắc phục sự cố trong đó canvas Xcode Previews không hiển thị chính xác khi sử dụng @_implementationOnly nhập. (86214393)
Xcode hiện hiển thị từng bản xem trước trên trang chuyên dụng của riêng nó bao gồm các điều khiển mới cho phép bạn thay đổi các cài đặt chung như bảng màu, hướng hoặc kích thước loại động mà không cần viết bất kỳ mã nào. (87357785)
Bản xem trước Xcode hiện có tính tương tác theo mặc định. Bạn có thể sử dụng trình chuyển đổi chế độ ở cuối canvas để chuyển đổi giữa các chế độ tương tác, lựa chọn và biến thể. (87358985)
Bản xem trước Xcode hiện hỗ trợ các biến thể xem trước: bản xem trước được tạo tự động cho phép bạn xem chế độ xem của mình ở nhiều dạng, kích thước loại hoặc hướng cùng một lúc mà không cần viết bất kỳ mã cấu hình nào. (88937848)
Bản xem trước hiện hoạt động trong các mô-đun bằng cách sử dụng @_spi. (89653122)
Sự cố trong bản xem trước hiện được phản ánh chính xác cho các dự án Swift Playgrounds được mở trong Xcode. (91716026)
Việc thay đổi đích chạy đang hoạt động hiện cập nhật chính xác canvas Xcode Previews. (92152617)
Cải thiện thời lượng pin khi sử dụng Xcode Previews. (92179785)
Các thay đổi đối với giá trị bằng chữ trong trình khởi tạo hiện được phản ánh chính xác trong Bản xem trước Xcode. (92599456)
Mục menu “Tự động làm mới canvas” trong canvas Xcode Previews đã được thay đổi để tạm dừng hành vi tạo tự động mới. Điều này cho phép làm mới bản xem trước theo cách thủ công ở nhịp mong muốn. (93261715)
Đã khắc phục một số sự cố với Bản xem trước Xcode không phản ánh các thay đổi mã dự kiến khi điều hướng đến các tệp không phải SwiftUI hoặc khi đóng canvas và mở lại. (93785410)
Bản xem trước Xcode hiện hỗ trợ các biến chứng watchOS bên trong các ứng dụng watchOS độc lập. (95311509)
Bản xem trước Xcode hiện hoạt động với các gói chứa tài nguyên không có trong ứng dụng. (96828503)
Deprecations
Hỗ trợ xem trước các tiện ích được tạo cho ứng dụng macOS và ứng dụng được tạo bằng Mac Catalyst đã bị xóa. (92531529) Giải pháp thay thế : Sử dụng Trình mô phỏng WidgetKit macOS.
Project Navigator
Vấn đề đã giải quyết
Đã sửa lỗi: Khi bạn sử dụng Trình kiểm tra tệp để thay đổi loại tệp, tệp có thể được mở lại trong trình chỉnh sửa khác. (91784648)
Đã sửa lỗi: Trình điều hướng sự cố không liệt kê các sự cố được tạo khi xây dựng mã Swift trong giai đoạn mô-đun phát ra. (92430695)
Vấn đề đã biết
Option+Clicking vào nút đóng cho tab trình chỉnh sửa không được chọn sẽ đóng tất cả các tab khác (như mong đợi) và cũng điều hướng đến tab được nhấp bằng điều hướng tùy chọn. Điều hướng này là không chủ ý. (96958005)
Việc mở gói kết quả có nhật ký bản dựng lớn có thể mất 30 giây trở lên. (97328772)
Option+Clicking vào tab trình chỉnh sửa không thực hiện “điều hướng tùy chọn” như được định cấu hình trong tùy chọn của Xcode. (97690500)
Refactoring
Các tính năng mới
Đã thêm một hành động tái cấu trúc để thêm một Codable triển khai rõ ràng. (87904700)
Vấn đề đã biết
Chuyển đổi sang Trình tạo Regex không xử lý chính xác các mẫu có chuỗi thoát Unicode. Ví dụ: một ký tự regex có chứa \u{0} sẽ được chuyển đổi thành "" kết quả. (95465206) (FB10343998)
Server
Vấn đề đã biết
Xcode Server không còn được hỗ trợ. (73888675)
Signing and Distribution
Các tính năng mới
Ký tự động hiện được hỗ trợ cho trình điều khiển DriverKit đã ký phát triển. Việc phân phối vẫn yêu cầu sự chấp thuận của Apple và cấu hình thủ công các khả năng bổ sung trên trang web Nhà phát triển của Apple . (81215709)
Quyền Game Center hiện có sẵn cho các ứng dụng trên iOS, watchOS và tvOS. Nếu ứng dụng của bạn sử dụng Game Center, ứng dụng sẽ tự động nhận được quyền này khi bạn tạo lại hồ sơ cấp phép của mình. Nếu bạn sử dụng ký tự động, Xcode sẽ tự động tạo hồ sơ cung cấp mới cho bạn. Nếu sử dụng ký thủ công, bạn cần đăng nhập vào tài khoản của mình và tạo lại hồ sơ cấp phép của mình Nếu bạn dự định tiếp tục sử dụng Game Center trong ứng dụng của mình, hãy thêm quyền com.apple.developer.game-center vào tệp quyền của bạn trong Xcode. Nếu không, hãy xóa capability trong Xcode và tắt Trung tâm trò chơi trên ID ứng dụng của bạn trong tài khoản nhà phát triển của bạn. (90667072)
Vấn đề đã biết
xcodebuild -exportArchivetrả về thông báo lỗi hết hạn phiên nếu bạn sử dụng khóa xác thực để tải ứng dụng của mình lên App Store Connect. (76036452)Giải pháp thay thế : Khóa xác thực chỉ được hỗ trợ để ký và cung cấp. Để tải ứng dụng lên từ xcodebuild, trước tiên hãy đăng nhập vào Xcode bằng ID Apple của bạn.
xcodebuildthỉnh thoảng có thể gặp sự cố trong DVTPortalEntitlementsManager. (98678163) (FB11267326)Cách giải quyết: Bạn có thể vô hiệu hóa logic lọc quyền lợi của Xcode để giải quyết vấn đề này, bằng cách chạy trong Terminal hoặc bằng cách thêm vào lời gọi dòng lệnh của bạn.defaults write com.apple.dt.Xcode DVTEnableMultiPlatformEntitlementFiltering -bool NO-DVTEnableMultiPlatformEntitlementFiltering=NOxcodebuild
Vấn đề đã giải quyết
Đã sửa lỗi: Khi chỉnh sửa khả năng của iCloud, Nhóm ứng dụng, Apple Pay hoặc Wallet, Xcode có thể đề nghị chọn một nhóm nếu bạn không chọn một nhóm. Việc chọn một nhóm khiến Xcode gặp sự cố. (93914533)
Simulator
Các tính năng mới
Simulator hiện hỗ trợ remote notifications trong iOS 16 khi chạy trong macOS 13 trên máy tính Mac có bộ xử lý Apple silicon hoặc T2. Simulator hỗ trợ môi trường Sandbox push notification services của Apple. Máy chủ của bạn có thể gửi thông báo từ xa tới ứng dụng của bạn đang chạy trong simulator đó bằng cách kết nối với sandbox APNS (api.sandbox.push.apple.com). Mỗi Simulator tạo mã thông báo đăng ký duy nhất cho sự kết hợp giữa trình mô phỏng đó và phần cứng máy Mac mà nó đang chạy trên đó. Xem Thông báo người dùng để biết thêm thông tin.Thông báo từ xa hỗ trợ nhiều tính năng hơn (như Tiện ích mở rộng dịch vụ thông báo) so với thông báo được mô phỏng cục bộ bằng cách sử dụng tệp .apns trọng tải hoặc lệnh đẩy simctl. Mã thông báo đăng ký thiết bị có độ dài thay đổi. Mã thông báo trong Trình mô phỏng có thể lớn hơn mã thông báo trên thiết bị vật lý hiện tại. Không mã hóa cứng bất kỳ độ dài hoặc định dạng cụ thể nào cho các mã thông báo này. (60974170)
simctl hiện hỗ trợ kiểm soát vị trí mô phỏng, bao gồm các kịch bản đang chạy và nội suy giữa danh sách các điểm tham chiếu. Xem xcrun simctl location để biết thêm thông tin. (59422559) (FB7577924)
Simulator hiện hỗ trợ hình ảnh đĩa thời gian chạy ngoài định dạng gói thời gian chạy hiện có. Ảnh đĩa được thêm vào vị trí lưu trữ do hệ thống quản lý được Bảo vệ tính toàn vẹn của hệ thống bảo vệ và được gắn tại các điểm gắn do hệ thống quản lý. Xem xcrun simctl thời gian chạy để biết thêm thông tin. (84169585)
simctl addmedia đã được cập nhật để hỗ trợ nhiều định dạng hình ảnh bổ sung (bao gồm nhiều định dạng RAW phổ biến). (87103990) (FB9832655)
Giờ đây, bạn có thể khởi động các thiết bị giả lập bằng cách sử dụng thời gian chạy chung là x86_64 trên máy Mac với Apple silicon bằng cách sử dụng --arch đối số dòng lệnh mới cho simctl boot. (88278366) (FB9860747)
Vấn đề đã biết
Nếu bạn ngắt kết nối hoặc tách hình ảnh đĩa thời gian chạy trình mô phỏng theo cách thủ công (chẳng hạn như bằng cách sử dụng diskutil eject hoặc umount), Trình mô phỏng và Xcode có thể không xác định được liệu thời gian chạy đã được cài đặt hay chưa. Nỗ lực tải xuống lại thời gian chạy dẫn đến lỗi với lỗi thời gian chạy trùng lặp. (89589210) Giải pháp thay thế : Việc khởi động lại khiến Trình mô phỏng gắn lại ảnh đĩa thời gian chạy. Ngoài ra, bạn có thể sử dụng xcrun simctl runtime để định vị ảnh đĩa thời gian chạy bị ảnh hưởng, xóa nó, sau đó sử dụng Xcode để tải xuống lại.
Hình ảnh đĩa thời gian chạy trình mô phỏng trong /tmp đó bạn thêm bằng cách sử dụng xcrun simctl runtime add sẽ bị xóa. (93858264) Giải pháp thay thế: Đặt hình ảnh đĩa thời gian chạy giả lập ở một nơi khác /tmp/trước khi chuyển nó vào xcrun simctl runtime add.
Siri Intents
Vấn đề đã biết
Mã Swift đã tạo mà trình biên dịch định nghĩa Ý định tạo ra sẽ phát ra cảnh báo khi được biên dịch:
method 'handle(intent:)' with Objective-C selector 'handleIntent:completion:' conflicts with method 'handle(intent:completion:)' with the same Objective-C selector; this is an error in Swift 6
(91852710)
Source Control
Vấn đề đã giải quyết
Nhiều lỗi ảnh hưởng đến độ chính xác và độ trễ của trạng thái tệp git trong bộ điều hướng Dự án và Thay đổi đã được giải quyết. Các thay đổi đối với tệp trong bản sao làm việc được phản ánh chính xác trong bộ điều hướng và trang cam kết mà không có độ trễ đáng kể. Ngoài ra, các tệp hiển thị các thanh thay đổi khi được trình bày trong trình chỉnh sửa sẽ nhận được trạng thái đã sửa đổi ngay sau khi thay đổi được thực hiện trong bộ nhớ, điều này sẽ khiến nó có trạng thái đã sửa đổi khi được lưu vào đĩa. (49909533)
Xcode hiện hỗ trợ tạo và sử dụng các khóa ED25519 và ECDSA được tạo bên ngoài để thực hiện gitcác thao tác SSH. (85009643)
Vấn đề đã biết
Định cấu hình ứng dụng mới trong Xcode Cloud sẽ không thành công khi nhóm nhà phát triển không có ứng dụng hiện có trong App Store Connect. (94199091)Giải pháp thay thế : Tạo ứng dụng trong App Store Connect trước, sau đó tích hợp sản phẩm trong Xcode.
Source Editor
Các tính năng mới
Xcode hiện ghim các thành phần cấu trúc mã của bạn lên đầu trình chỉnh sửa khi bạn cuộn qua tài liệu. Để chuyển đổi hành vi này, hãy sử dụng “Hiển thị: Cấu trúc mã trong khi cuộn” trong tùy chọn Chỉnh sửa văn bản của Xcode. (10582250)
Các lỗi trong tệp Swift hiện cung cấp bản sửa lỗi để thêm các lần nhập bị thiếu. (21533417) (FB5562997)
Gói mã bằng câu lệnh if giờ đây sẽ tự động xác định lại khối. (29215201)
Trình khởi tạo hiện được trình bày dưới dạng hoàn thành mã toàn cục trong Swift. (60399329)
Giao diện người dùng để chuyển đến các định nghĩa ký hiệu hoặc trình gọi hiện cung cấp cho bạn một mẫu mã từ mỗi vị trí. (69467155)
Việc hoàn thành mã hiện thu gọn các chức năng quá tải thành một hàng. (81338102)
Đã thêm hỗ trợ chỉnh sửa và đánh dấu cú pháp cho Biểu thức chính quy Swift. Giờ đây, bạn có thể chuyển đổi các ký tự biểu thức chính quy sang trình tạo biểu thức chính quy tương đương bằng cách sử dụng Trình chỉnh sửa > Tái cấu trúc > Chuyển đổi sang Trình tạo Regex. Khi di chuyển điểm chèn bên trong một biểu thức chính quy, cấu trúc con kèm theo của biểu thức chính quy được tô sáng. (82540073)
Xcode hiện cung cấp mẫu tệp để chọn tham gia các lựa chọn thay thế cảm ứng cho ứng dụng iOS của bạn. Bạn có thể sử dụng các lựa chọn thay thế cảm ứng để tương tác với ứng dụng của mình trên máy Mac có silicon của Apple – ví dụ: nhấn và giữ phím Tùy chọn để sử dụng bàn di chuột làm màn hình cảm ứng ảo. Để bật, hãy chọn Tệp > Tệp mới > iOS > Tài nguyên > Các lựa chọn thay thế cảm ứng và định cấu hình tệp mới được thêm com.apple.uikit.inputalternatives.plist để chọn các lựa chọn thay thế cảm ứng cho ứng dụng của bạn. (84271952)
Hoàn thành mã trong Swift hiện cung cấp đoạn mã khởi tạo theo thành viên. (84348512)
Hoàn thành mã trong Swift hiện cung cấp các đoạn trích cho các if case câu lệnh. (84381718)
Giờ đây, bạn có thể chọn bất kỳ tổ hợp tham số mặc định nào khi hoàn thành mã bằng cách nhập để khớp với tên tham số. (84906871)
Cải thiện độ chính xác của việc hoàn thành mã trong Swift. (85090778)
Khi chỉnh sửa mã, mục menu Chỉnh sửa > Nhân bản và phím tắt tương ứng của nó giờ đây sẽ sao chép văn bản đã chọn — hoặc dòng hiện chứa điểm chèn, nếu không có văn bản nào được chọn. (8614499) (FB5618491)
Hoàn thành mã trong Swift hiện cung cấp các đoạn mã để thêm Codable triển khai rõ ràng. (87904617)
Xcode hiện bản địa hóa các phím tắt cho tất cả các bàn phím phần cứng quốc tế để có khả năng truy cập tốt hơn. Bạn có thể tùy chỉnh thêm điều này trong cài đặt Key Bindings. (88397421)
Hoàn thành mã trong Swift hiện cung cấp các đoạn mã cho map, filter và contains, dựa trên tên biến. (89717471)
Cấu trúc mã được ghim vào đầu trình chỉnh sửa với tùy chọn “Hiển thị: Cấu trúc mã trong khi cuộn” mới của Xcode 14 hiện bao gồm thông tin bổ sung cho các khai báo được ngắt dòng. (93591165)
Vấn đề đã giải quyết
Đã sửa lỗi: Hoàn thành mã tại các trang web gọi hàm trong Swift hiện chèn các đối số bị thiếu. (9293666)
Một số cải tiến hiệu suất đã được thực hiện để xem và chỉnh sửa các tệp lớn. Các sự cố thường xuất hiện nhất trong tệp nguồn được tạo tự động và tệp thử nghiệm với số lượng lớn thử nghiệm và có thể tồi tệ hơn khi hiển thị dải băng gấp mã. (57789416)
Hiệu suất khi sử dụng Minimap trong các tệp HTML với các dòng rất dài đã được cải thiện. (58893150)
Đã khắc phục sự cố khiến hiệu suất bị giảm khi nhập tệp có nhiều lỗi hoặc cảnh báo. (59084580)
Trình kiểm tra Trợ giúp nhanh hiện hiển thị các mô tả về cài đặt bản dựng trong trình chỉnh sửa cài đặt bản dựng dưới dạng văn bản có định dạng với các liên kết có thể nhấp. (60067884)
Hoàn thành mã không còn tự động nhập các mô-đun. (78136559)
Xcode hiện ưu tiên các loại có thể được sử dụng làm thuộc tính khi gọi hoàn thành mã sau @. (78239501)
Đã khắc phục sự cố khiến khoảng trắng xuất hiện sau một số vùng mã được gấp lại. (78333320) (FB9114110)
Đã khắc phục sự cố trong đó Trình chỉnh sửa nguồn bị chèn nhầm *khi tạo một dòng mới sau nhận xét tài liệu kiểu khối. (79415983)
Đã thêm giao diện người dùng mới để chuyển đến các định nghĩa biểu tượng hoặc trình gọi tập trung vào thông tin phân biệt về từng vị trí. (81366453)
Cải thiện tốc độ và tính chính xác của việc hoàn thành mã trong các biểu thức phức tạp và SwiftUI. (83435550)
Xcode ưu tiên các loại Chế độ xem SwiftUI khi bạn nhập bên trong trình tạo chế độ xem SwiftUI. (83846531)
Đã khắc phục sự cố khi di chuột qua văn bản trong trình chỉnh sửa Đánh giá mã có thể làm Xcode bị lỗi. (85239396)
Đã khắc phục sự cố trong đó các cảnh báo cũ hoặc lỗi gạch chân không chính xác các phần của dòng mà chúng được đính kèm. (86225773)
Đoạn mã hoàn thành mã động liên quan đến vòng lặp for không còn đề xuất đặt tên các phần tử được lặp lại giống hệt với vùng chứa của chúng. (87167378)
Gấp một tệp Swift bằng cách sử dụng mục menu Phương pháp và chức năng gấp hoặc phím tắt được liên kết của nó giờ đây cũng gấp các thuộc tính và chỉ số được tính toán. (87692952) (FB9849362)
Hoàn thành mã trong SwiftUI hiện cung cấp đoạn mã cho List và ForEach. (87904499)
Đã khắc phục sự cố có thể dẫn đến giảm hiệu suất theo thời gian khi chỉnh sửa các tệp dài có bật Bản đồ thu nhỏ. (89916018)
Đã khắc phục sự cố khi sử dụng tính năng “Hiển thị: Cấu trúc mã trong khi cuộn” mới của Xcode 14 với con trỏ khối có thể dẫn đến tạo tác trực quan. (89973125)
Các khai báo Swift actor hiện xuất hiện trong Bản đồ nhỏ cùng với các khai báo lớp và cấu trúc. (90279950)
Đã khắc phục sự cố khi hoàn thành mã trong Swift hiển thị các ký hiệu không thể truy cập được. (90404828)
Đã khắc phục các sự cố khác nhau trong đó các biểu tượng hệ thống không được đánh dấu cú pháp trong các tệp Swift. (91654823)
Hoàn thành mã trong Swift hiện ưu tiên tốt hơn các API hệ thống phổ biến. (91977150)
Đã khắc phục sự cố có thể dẫn đến sự cố khi chuyển đến định nghĩa ký hiệu trong theo dõi GPU. (93434935)
Tô màu cú pháp của chữ regex đã được cập nhật để hỗ trợ đề xuất tiến hóa nhanh mới nhất SE-0354 Chữ regex . Đặc biệt, giờ đây nó xử lý chính xác những gì trông giống như các ký tự không được đóng dấu, theo sau là một nhận xét, các ký tự được sử dụng trong try và các await biểu thức, đồng thời phân định tốt hơn với các toán tử tiền tố có chứa phần mở rộng /. (93673226, 92355356, 94661164) (95146866)
Vấn đề đã biết
Chuyển đổi sang Trình tạo Regex không xử lý chính xác các mẫu bằng tính năng tra cứu. Ví dụ: /foo(?=bar)/nên tạo ra Lookahead("bar")kết quả, nhưng chỉ tạo ra "bar". (97208700)
StoreKit
Vấn đề đã giải quyết
Xcode hiện có khả năng đồng bộ hóa các sản phẩm mua trong ứng dụng từ App Store Connect vào các tệp cấu hình StoreKit để thử nghiệm StoreKit nhanh hơn trong thiết lập Xcode. Ngoài ra còn có một trình quản lý giao dịch được cập nhật với tính năng lọc và trình kiểm tra giao dịch. (83863948)
Đã khắc phục sự cố khi gọi phương thức ‘clearTransactions()’ trong SKTestSession mà không xóa tất cả các giao dịch SKPaymentQueue khi thử nghiệm ứng dụng bằng API gốc để mua hàng trong ứng dụng. (86696132) (FB9814502)
Đã sửa lỗi: Trình quản lý giao dịch không còn hiển thị cảnh báo chưa hoàn thành đối với các giao dịch không thể hoàn thành khi sử dụng thử nghiệm StoreKit trong Xcode. (89419046) (FB9927448)
Vấn đề đã biết
Việc sử dụng các thuộc tính và phương pháp StoreKit sau đây trên các ứng dụng có mục tiêu triển khai tối thiểu bên dưới iOS 16, macOS 13, watchOS 9 và tvOS 16 sẽ khiến ứng dụng gặp sự cố khi khởi chạy khi chạy trên các hệ thống cũ hơn iOS 16, macOS 13, watchOS 9 và tvOS 16:
priceFormatStylevà trên các giá trịsubscriptionPeriodFormatStyleProduct
environmentStringRepresentationvà trên các giá trịrecentSubscriptionStartDateProduct.SubscriptionInfo.RenewalInfo
environmentStringRepresentationvề Transactiongiá trị
dateRange(referenceDate:)và trên các giá trị (99962885) (FB11516463)formatted(_:referenceDate:)Product.SubscriptionPeriod
Giải pháp thay thế : Đối với mỗi mục tiêu sử dụng API StoreKit được liệt kê ở trên, hãy điều hướng đến tab “Giai đoạn xây dựng” trong trình chỉnh sửa dự án với mục tiêu được chọn và thêm StoreKit.framework trong “Liên kết nhị phân với thư viện” nếu nó chưa có. Đặt cột “Trạng thái” thành “Tùy chọn”.
Xcode 14 bao gồm Swift 5.7 và SDK cho iOS 16, iPadOS 16, tvOS 16, watchOS 9 và macOS Monterey 12.3. Bản phát hành Xcode 14 hỗ trợ gỡ lỗi trên thiết bị trong iOS 11 trở lên, tvOS 11 trở lên và watchOS 4 trở lên. Xcode 14 yêu cầu máy Mac chạy macOS Monterey 12.5 trở lên.
Thay đổi chung
Các tính năng mới
Xcode 14 cho phép một target duy nhất hỗ trợ nhiều nền tảng và bao gồm có điều kiện các dependencies, code, resource và build settings cho các nền tảng cụ thể. (74664328)
Xcode 14 hỗ trợ phát triển trình điều khiển DriverKit cho iPadOS. (81117498)
Xcode 14 bao gồm một mẫu mặc định cho các ứng dụng watchOS kết hợp các mục tiêu Ứng dụng WatchKit và Tiện ích mở rộng ứng dụng WatchKit thành một mục tiêu Ứng dụng Watch duy nhất, đơn giản hóa mã, nội dung và quản lý bản địa hóa. Bạn có thể triển khai các ứng dụng watchOS một mục tiêu cho watchOS 7 trở lên. (83222217)
Vấn đề đã giải quyết
Đã sửa lỗi: Khi storyboard đang mở và nội dung watchOS hoặc tvOS bị xóa khỏi Cài đặt Xcode, storyboard sẽ không đóng. (87471381)
Vấn đề đã biết
Nếu một file Package.swift được thêm vào thư mục chứa trong khi thư mục đó đang mở trong Xcode, Package sẽ không được nhận dạng. (85075018) Cách giải quyết : Thoát và chạy lại Xcode.
CGFLOAT_EPSILON không còn luôn là Float trên watchOS và nó có thể gây ra sự cố biên dịch. (88698530) Cách giải quyết: Chuyển đổi nó trước sang CGFloat bằng cách sử dụng trình khởi tạo CGFloat(CGFLOAT_EPSILON). Hiện được hỗ trợ trên cả nền tảng 32 và 64 bit
Xcode 14 có thể không tìm thấy các công cụ bằng cách sử dụng xcodebuild -find(được sử dụng bởi xcrun và các trình bao bọc /usr/bin chẳng hạn như /usr/bin/clang) nếu nội dung khởi chạy lần đầu chưa được cài đặt. (98008921) Cách giải quyết : Chạy hoặc khởi chạy Xcode.app trước .xcodebuild -runFirstLaunch
Xcode đôi khi có thể coi thời gian chạy sim của nền tảng bị thiếu khi thực hiện Đăng xuất và Đăng nhập của người dùng. (99200503) Cách giải quyết : Khởi động lại máy.
Khi chạy trong macOS 13 beta, mã AppIntents có thể không xây dựng được bằng Xcode 14. (99661742) (FB11470314) Cách giải quyết : Tạo mã AppIntents với Xcode 14 beta 6 hoặc trên máy Mac chạy macOS Monterey 12 với Xcode 14.
Không thể sử dụng Xcode 14 với iOS 15.7 để phát triển. (99847608) Cách giải quyết: Sử dụng Xcode 13.4.1 với iOS 15.7.
Trình biên dịch Apple Clang
Các tính năng mới
Các dự án C++ mới mà bạn tạo trong Xcode sử dụng phương ngữ ngôn ngữ mặc định là C++20. (93456065)
Một số C++20 và C++2b papers đã được triển khai:
Các C++20 papers đã triển khai:
P0692R1 – Kiểm tra quyền truy cập vào chuyên môn
P0388R4 – Cho phép chuyển đổi thành mảng có giới hạn không xác định
Các C++2b papers đã được triển khai:
P1938R3 –if consteval
P1401R5 – Thu hẹp chuyển đổi theo ngữ cảnh thành bool
P1949R7 – Cú pháp mã định danh C++ sử dụng Unicode Standard Annex 31
P2360R0 – Mở rộng init để cho phép khai báo bí danh (93898598)
Vấn đề đã giải quyết
Đã sửa lỗi: Xcode không cung cấp tính năng làm nổi bật ngữ nghĩa và hỗ trợ chuyển sang định nghĩa cho các khai báo khái niệm C++20 và các mệnh đề yêu cầu trong các mẫu. (93046529)
Tính năng không còn khả dụng trên XCode14
Bắt đầu với Xcode 14, bitcode không còn cần thiết cho các ứng dụng watchOS và tvOS và App Store không còn chấp nhận gửi bitcode từ Xcode 14. Xcode không còn xây dựng mã bit theo mặc định và tạo thông báo cảnh báo nếu một dự án kích hoạt rõ ràng mã bit: “Việc xây dựng bằng mã bit không được dùng nữa. Vui lòng cập nhật cài đặt dự án và/hoặc mục tiêu của bạn để tắt mã bit.” Khả năng xây dựng bằng mã bit sẽ bị xóa trong bản phát hành Xcode trong tương lai. IPA có chứa mã bit sẽ bị tước mã bit trước khi được gửi tới App Store. Chỉ có thể tải xuống các biểu tượng gỡ lỗi từ App Store Connect / TestFlight cho các lần gửi mã bit hiện có và không còn khả dụng cho các lần gửi được thực hiện bằng Xcode 14. (86118779)
Danh mục tài sản
Các tính năng mới
Đơn giản hóa biểu tượng ứng dụng bằng một hình ảnh 1024×1024 được tự động thay đổi kích thước cho mục tiêu của nó. Chọn tùy chọn Single Size trong tab Attributes inspector của mục app icon. Bạn vẫn có thể ghi đè các kích thước riêng lẻ bằng tùy chọn All Sizes. (18475136) (FB5503050)
Giờ đây, bạn có thể dán trực tiếp hình ảnh đã sao chép từ Finder vào asset catalog outline. (58980721)
Giờ đây, bạn có thể nhấp đúp vào một vị trí hình ảnh để hiển thị bảng điều khiển tệp đang mở và chọn nội dung thay thế. (81365822)
Bạn có thể chỉ định chế độ kết xuất mặc định cho các ký hiệu tùy chỉnh trong Asset Catalog. Đặt thuộc tính Kết xuất dưới dạng thành tự động, mẫu, nhiều màu hoặc phân cấp. Sau đó, hệ thống sẽ sử dụng chế độ kết xuất mặc định cho biểu tượng, trừ khi bạn ghi đè rõ ràng chế độ đó. Để biết thêm thông tin về biểu tượng tùy chỉnh, hãy xem Tạo hình ảnh biểu tượng tùy chỉnh cho ứng dụng của bạn . (84513859)
Vấn đề đã giải quyết
Chế độ “Tất cả kích thước” cho các biểu tượng ứng dụng đã được làm linh hoạt hơn và bao gồm một số kích thước bổ sung chưa được sử dụng trước đây. Chế độ “Tất cả kích thước” không yêu cầu phải lấp đầy tất cả các vị trí, kích thước chỉ có thể được cung cấp khi cần. Các biểu tượng ứng dụng được tạo bằng Xcode 13 trở về trước có thể được chuyển đổi từ chế độ “Tất cả kích thước (Xcode 13)” thành “Tất cả các kích thước” hoặc “Một kích thước”. (93682080)
Vấn đề đã biết
Các ứng dụng sử dụng biểu tượng ứng dụng có kích thước đơn lẻ có thể không xác thực được App Store nếu mục tiêu triển khai cũ hơn iOS 12 hoặc watchOS 4. (98471456)
Build System
Các tính năng mới
Xcode cung cấp một trình chỉnh sửa trợ lý mới cho nhật ký bản dựng tập trung vào tính song song để giúp xác định các vấn đề về hiệu suất bản dựng. Hình ảnh trực quan này hiển thị các sự kiện dưới dạng lưới các khối màu trong đó trục dọc biểu thị mức độ song song và trục ngang biểu thị thời gian. (47858322)
Xcode 14 hiện có thể biên dịch các mục tiêu song song với các phụ thuộc mục tiêu Swift của chúng. (57116972)
Trình điều khiển Swift, thành phần phối hợp các yêu cầu giao diện người dùng Swift, hiện được tích hợp vào hệ thống xây dựng của Xcode, cho phép phụ thuộc chi tiết hơn vào các tác vụ hệ thống xây dựng khác và lập lịch trình rõ ràng. (72440175)
Trong Build Phases, giờ đây bạn có thể chỉnh sửa hàng loạt tệp trong chế độ xem bảng nhiều lựa chọn. Khi bạn chỉnh sửa cột bộ lọc nền tảng của bảng đó, hệ thống sẽ áp dụng các thay đổi cho tất cả các tệp trong lựa chọn. (80683128) (FB9340886)
Các mục tiêu thư viện động và khung chỉ dành cho Swift có thể chọn tham gia tối ưu hóa hệ thống bản dựng mới bằng cách sử dụng cài đặt bản dựng EAGER_LINKING. Khi bạn bật tính năng này, Xcode sẽ phát ra các thành phần tạo tác bổ sung trong quá trình biên dịch Swift, cho phép Xcode bỏ chặn liên kết của các mục tiêu xuôi dòng trước đó, tăng tính song song trong các bản dựng. (82396635)
Hệ thống xây dựng chạy song song các tác vụ từ các giai đoạn xây dựng khác nhau khi các yếu tố phụ thuộc đầu vào và đầu ra không thực thi thứ tự của chúng. Bạn có thể chọn tham gia hành vi mới này cho các giai đoạn xây dựng tập lệnh chạy bằng cách sử dụng cài đặt bản dựng FUSE_BUILD_SCRIPT_PHASES. (82396977)
App Store hiện hỗ trợ làm mỏng ứng dụng cho các trình đổ bóng Metal được biên dịch sẵn. (82902821)
Các bản dựng Xcode cho thiết bị watchOS hiện bao gồm kiến trúc arm64 theo mặc định. (83319300)
Đã thêm toán tử thay thế :relativeto=macro mới cho cài đặt bản dựng mà bạn có thể sử dụng để tính toán đường dẫn tương đối từ đường dẫn này sang đường dẫn khác; Ví dụ: $(INSTALL_PATH:relativeto=/usr/lib) Chỗ INSTALL_PATH là “ /usr/bin..” và đánh giá là “../lib ”. Bạn có thể sử dụng quy tắc này trong quy tắc xây dựng để sao chép một loạt tham chiếu tệp trong khi vẫn duy trì phân cấp thư mục của chúng trong thư mục đích hoặc để tính toán mục tiêu dự kiến rpaths bằng cách sử dụng đường dẫn tương đối giữa đường dẫn cài đặt của chính nó và đường dẫn cài đặt đã biết của các thành phần phụ thuộc. (88293015)
Xcode hiện cung cấp các cài đặt RECOMMENDED_MACOSX_DEPLOYMENT_TARGET, RECOMMENDED_IPHONEOS_DEPLOYMENT_TARGET, RECOMMENDED_TVOS_DEPLOYMENT_TARGET, RECOMMENDED_WATCHOS_DEPLOYMENT_TARGET, RECOMMENDED_DRIVERKIT_DEPLOYMENT_TARGET và xây dựng cho biết các phiên bản triển khai tối thiểu được đề xuất cho từng nền tảng Xcode được hỗ trợ. (90464341)
Giờ đây, bạn có thể bật Sandbox cho các giai đoạn xây dựng tập lệnh shell bằng cách sử dụng ENABLE_USER_SCRIPT_SANDBOXING trong build settings. Sandbox chặn quyền truy cập vào các tệp bên trong gốc nguồn của dự án cũng như thư mục Dữ liệu được tạo trừ khi bạn liệt kê các tệp đó dưới dạng đầu vào hoặc đầu ra. Khi được bật, quá trình xây dựng không thành công do vi phạm Sandbox nếu một giai đoạn tập lệnh cố gắng đọc hoặc ghi vào một phần phụ thuộc không được khai báo, ngăn chặn các quá trình xây dựng không chính xác. (90506067)
Vấn đề đã giải quyết
Đã khắc phục sự cố trong đó nhiều dự án Xcode tham chiếu cùng một xcconfig (do đó bao gồm một xcconfig khác) đã tính toán không chính xác cùng một cài đặt bản dựng trên cả hai dự án. (84319288) (FB9707003)
Đã sửa lỗi: đích chạy của thiết bị watchOS không còn xuất hiện hai lần trong menu đích chạy. (85635959)
Cải thiện tốc độ tải của không gian làm việc lớn có nhiều mục tiêu chia sẻ .xcconfigtệp. (85985712)
Đã sửa lỗi: Khi bạn lưu trữ ứng dụng watchOS với kiến trúc arm64e được bật, ứng dụng đó không thể xây dựng. (93550623)
Vấn đề đã biết
Báo cáo sự cố từ các ứng dụng Mac Catalyst không được ký hiệu đầy đủ trong Xcode Organizer. (94820955)
Tính năng bị loại bỏ
Vì mã bit hiện không được dùng nữa nên các bản dựng cho iOS, tvOS và watchOS không còn bao gồm mã bit theo mặc định. (87590506)
Đã thêm cài đặt bản dựng mới, SWIFT_TOOLS_DIR, thay thế SWIFT_EXEC làm cơ chế được đề xuất để sử dụng tệp thực thi giao diện người dùng nhanh tùy chỉnh. SWIFT_TOOLS_DIR phải được đặt thành đường dẫn của thư mục chứa giao diện người dùng nhanh và các công cụ liên quan. Lưu ý rằng vì trình điều khiển Swift hiện được tích hợp trong hệ thống xây dựng Xcode, không thể sử dụng SWIFT_TOOLS_DIR và SWIFT_EXEC để chỉ định nhị phân trình điều khiển tùy chỉnh trừ khi SWIFT_USE_INTEGRATED_DRIVER được đặt thành NO. (88967344)
Hệ thống xây dựng cũ đã bị xóa. (90801041)
Việc xây dựng các dự án iOS với các mục tiêu triển khai cho kiến trúc armv7, armv7s và i386 không còn được hỗ trợ nữa. (92831716)
Bản phát hành xây dựng để triển khai cho hệ điều hành cũ hơn macOS 10.13, iOS 11, tvOS 11 và watchOS 4 không còn được hỗ trợ. (92834476)
Khi xóa một điều kiện khỏi trình chỉnh sửa biến thể, giá trị sẽ không được duy trì. (98149034) Giải pháp thay thế: Sử dụng Trình chỉnh sửa cài đặt bản dựng để xóa điều kiện.
Debugging
Các tính năng mới
Trình gỡ lỗi biểu đồ bộ nhớ hiện hiển thị tất cả các tham chiếu đến và đi trong biểu đồ bộ nhớ. Bạn có thể điều chỉnh tập hợp các nút hiển thị trong cửa sổ bật lên mới. (69454709)
LLDB hiện hiển thị các cập nhật tiến độ trong Xcode và dòng lệnh cho các hoạt động chạy dài. (73511008)
Giờ đây, bạn có thể gọi tập lệnh nhật ký sự cố của LLDB bằng xcrun crashlog <path/to/crash>. (79991815)
Trình kiểm tra hiệu suất luồng hiển thị các vấn đề về hiệu suất thời gian chạy trong Trình điều hướng sự cố và trình chỉnh sửa nguồn trong khi gỡ lỗi ứng dụng. Chọn hộp kiểm Trình kiểm tra hiệu suất luồng trong Lược đồ chạy của mục tiêu ứng dụng của bạn để bật tính năng này. (80048810)
Giờ đây, bạn có thể mở một .xcresult cho một hành động scheme khởi chạy. (88932007)
Xcode hiện hiển thị nhật ký khởi chạy mới trong Trình điều hướng báo cáo. Nhật ký cho biết các hành động mà Xcode thực hiện để cài đặt, khởi chạy và gỡ lỗi. (90859910)
Vấn đề đã giải quyết
Cải thiện hiệu suất khi gỡ lỗi chương trình Swift trên thiết bị iOS. Thay vì sao chép siêu dữ liệu phản ánh từ thiết bị iOS, giờ đây Xcode có thể đọc siêu dữ liệu từ đĩa. (73179144)
Xcode hiện nhóm các mục nhật ký cho cùng một hành động sơ đồ khởi chạy trong trình điều hướng Báo cáo. (87216988)
Đã sửa lỗi: Thay vào đó, bước vào chức năng không đồng bộ Swift trong LLDB sẽ hoàn thành chức năng hiện tại. (88142757)
Vấn đề đã biết
Không thể đính kèm trình gỡ lỗi vào Tiện ích mở rộng tiện ích màn hình khóa. (93941779) Giải pháp thay thế: Chỉnh sửa Extension scheme – trong tab Run action, tab Argument, hãy đặt biến môi trường ‘_XCWidgetKind’ thành một trong các tên class/struct của bạn khi triển khai widget.
Tài liệu
Các tính năng mới
Swift-DocC trong Xcode hiện hỗ trợ viết và xây dựng tài liệu cho API Objective-C và C. (58760015)
Các trang web tài liệu Swift-DocC mà Xcode 14 tạo ra bao gồm một thanh bên điều hướng mới để khám phá và lọc tài liệu. (89031049)
Theo mặc định, tài liệu Swift-DocC mà Xcode 14 tạo ra hiện tương thích với hầu hết các dịch vụ lưu trữ được quản lý, bao gồm Trang GitHub. (91173450)
Vấn đề đã giải quyết
Đã sửa lỗi: Swift-DocC đưa ra chẩn đoán không chính xác về các lệnh không được hỗ trợ trong tài liệu nguồn biểu tượng khi xây dựng tài liệu cho các dự án Objective-C bao gồm các lệnh Doxygen. (90953916)
Đã sửa lỗi: Tài liệu Mục tiêu-C cho các API đa ngôn ngữ hiển thị các mối quan hệ ký hiệu Swift thay vì các mối quan hệ Mục tiêu-C. (91627374)
Đã sửa lỗi: Swift-DocC đưa ra chẩn đoán không chính xác về các liên kết ký hiệu không thể giải quyết đối với tài liệu kế thừa từ các ký hiệu trong các mô-đun khác. (92185538)
Đã sửa lỗi : Không thể xây dựng tài liệu cho các loại phù hợp với ChartContent protocol khi conform Swift Charts. (93610106)
Instruments
Các tính năng mới
Các hàm tổng hợp hoạt động trên các khoảng thời gian hiện tính toán chính xác hơn kết quả của chúng khi bộ lọc thời gian đang hoạt động, chỉ dựa trên phần của khoảng thời gian tổng thể giao với bộ lọc thời gian hiện tại. (32330648)
Bộ lọc chi tiết hiện cho phép áp dụng các bộ lọc cho một cột cụ thể khi xem Chế độ xem danh sách. Có thể thêm các bộ lọc đã nhập này bằng cách sử dụng menu ngữ cảnh trên các giá trị được hiển thị hoặc bằng cách nhập mã thông báo rồi chọn loại mã thông báo. (56080914)
Các rãnh Chủ đề chính hiện được sắp xếp đầu tiên khi hiển thị các rãnh trong dòng thời gian của Nhạc cụ. (59876638)
Công cụ có công cụ Theo dõi treo mới hiển thị khi chuỗi chính của ứng dụng không thể xử lý các sự kiện đến trong một khoảng thời gian dài, có khả năng khiến giao diện người dùng bị treo. Ngoài ra, các công cụ Time Profiler và CPU Profiler cũng hiển thị khả năng bị treo. (65694830)
Biểu đồ biểu đồ trong Công cụ hiện hiển thị thời lượng của phạm vi thời gian được sử dụng để tổng hợp dữ liệu trong chú giải công cụ di chuột qua. (65684291)
Chế độ xem danh sách của công cụ hiện hiển thị nhãn trạng thái ở dưới cùng, báo cáo về số lượng hàng hiện có và được chọn trong chế độ xem hiện tại. (82652407)
Mẫu Core ML mới có sẵn trong Công cụ. Mẫu này bao gồm các công cụ Core ML và Neural Engine mới cùng với các công cụ GPU và Time Profiler. Sử dụng mẫu này để giúp lập hồ sơ sử dụng Core ML và hiểu cách các mô hình của bạn đang chạy trên thiết bị. Việc kết hợp thông tin từ Core ML, Công cụ thần kinh và Công cụ GPU có thể giúp theo dõi những hoạt động nào được thực thi trên phần cứng được tăng tốc. Dữ liệu thời gian tổng hợp có sẵn cho từng sự kiện, mô hình và mô hình con. (83123510)
Bạn có thể mở một menu ngữ cảnh mới bằng các khoảng thời gian bấm Điều khiển trong dòng thời gian. Menu cung cấp các hành động để đặt bộ lọc thời gian theo khoảng thời gian đã chọn hoặc hiển thị thông tin tương ứng trong khu vực chi tiết. (86728567)
Chế độ xem Thông tin Chạy của Dụng cụ hiện bao gồm kiến trúc của tệp nhị phân đích khi ghi một dấu vết quy trình. (89733709)
Instruments có một công cụ Runloop mới hiển thị việc sử dụng runloop và các lần lặp lại riêng lẻ, đồng thời phân biệt trực quan các chế độ ngủ của runloop và các khoảng thời gian bận cho tất cả các runloop trong một quy trình. (89746568)
Các giá trị trong chế độ xem chi tiết tổng hợp hoặc danh sách giờ đây sẽ được tô sáng khi bạn trỏ con trỏ qua chúng và bạn có thể giữ Control khi bấm vào một giá trị để xem menu ngữ cảnh. Menu này hiển thị các hành động để sao chép giá trị, đặt bộ lọc chi tiết và thêm hoặc ghim rãnh nếu giá trị có đại diện cho rãnh. (90817779)
Các công cụ hiện bao gồm một mẫu Đồng thời Swift mới để theo dõi cách sử dụng và hành vi của các nguyên mẫu đồng thời của Swift. Mẫu chứa:
Một công cụ Swift Tasks mới hiển thị các trạng thái nhiệm vụ theo thời gian, tóm tắt các trạng thái nhiệm vụ, cung cấp tường thuật chi tiết về nhiệm vụ, minh họa các mối quan hệ đồng thời có cấu trúc và xây dựng cây cuộc gọi của các ngăn xếp công việc tạo nhiệm vụ.
Một công cụ Swift Actors mới để theo dõi hành vi tác vụ giữa các tác nhân hiển thị hàng đợi tác vụ cho từng tác nhân và giúp chẩn đoán các sự cố với mã và tranh chấp được cô lập bởi tác nhân.Các công cụ này yêu cầu thiết bị trong thời gian chạy đồng thời Swift lần đầu tiên có sẵn trong macOS 13, iOS 16, tvOS 16 và watchOS 9 trở lên. (69852855)
heap, leaks, and Instruments’ leaks tool hiện báo cáo ít tham chiếu sai hơn bắt nguồn từ phân bổ liên quan đến đồng thời Swift và xác định đây là phân bổ Swift AsyncTask. (72907112)
leaks --atExit -- <command> giờ đây sẽ tự động đặt trong môi trường MallocStackLogging=lite cho lệnh đã chỉ định để Công cụ có thể hiển thị dấu vết ngược ngăn xếp cho phân bổ bị rò rỉ. Để sử dụng một cài đặt khác của biến môi trường đó (chẳng hạn như CÓ hoặc KHÔNG), hãy đặt biến môi trường trước khi chạy leaks. (73779272)
xctrace bây giờ sẽ gửi thông báo Darwin khi bắt đầu theo dõi, thông báo này nên được ưu tiên thay vì đọc đầu ra tiêu chuẩn của lệnh. Để sử dụng nó, hãy chỉ định notify-tracing-started tùy chọn có tên thông báo mong muốn và sử dụng notify_register_dispatch để nhận thông báo không đồng bộ trong tập lệnh của bạn. (75745933)
Cải thiện hiệu suất phân tích của mẫu Dấu vết Hệ thống. Các công cụ hiện hiển thị các bản ghi cho người dùng nhanh hơn; tuy nhiên, khi theo dõi một quy trình đơn lẻ, bạn không thể thấy trạng thái luồng cho các quy trình khác. Thay vào đó, để xem trạng thái của các quy trình khác, hãy theo dõi “Tất cả các quy trình”. (79904471)
Đối với các quy trình đang chạy MallocStackLogging được bật, heap, leaks và trình gỡ rối bộ nhớ Xcode hiển thị các tên loại dành cho phân bổ không đối tượng ở dạng malloc in <function name> trong đó “function name” là khung ngăn xếp không phân bổ đầu tiên. Tính năng này giúp xác định rõ hơn mục đích của bộ nhớ phi đối tượng. (85598783)
heap -addresses=all hiện hiển thị các mô tả của các đối tượng CFData có chứa các .plisttệp nhị phân theo cách dễ đọc hơn đối với con người. (88466642)
Vấn đề đã giải quyết
heap , leaks, và stringdups bây giờ bao gồm nội dung của một số loại chuỗi Swift được cấp phát theo đống trong phần mô tả về cấp phát của các chuỗi đó. (14902403)
Công cụ Hiệu suất GCD hiện diễn giải chính xác các sự kiện có mã “7” và “9”. (69721960)
Thiết kế trình xem nguồn trong Công cụ bao gồm các cải tiến để hiển thị dữ liệu hiệu suất tốt hơn:
Có một chế độ Xen kẽ mới để xem mã nguồn và tháo gỡ liên kết với nhau, giúp việc liên kết các hướng dẫn được tạo cho từng dòng mã nguồn trở nên dễ dàng hơn.
Trình xem nguồn hiện hiển thị các giá trị được lấy mẫu được gán cho một dòng mã hoặc phân tách trong một cột riêng biệt thay vì dưới dạng chú thích dòng.
Trình xem nguồn hiện hiển thị Bộ đếm CPU, sự kiện PMC và công thức động được định cấu hình trong các tùy chọn ghi bên cạnh nguồn và tháo gỡ. (80232392)
Đã khắc phục sự cố trong đó Chế độ xem nguồn trong Công cụ đôi khi không hiển thị chú thích hiệu suất khi được mở từ chế độ xem Sơ đồ cuộc gọi. (80501587)
Khi sử dụng Instruments Source Viewer ở chế độ xem tách, việc chọn tháo gỡ bây giờ cũng chọn nguồn tương ứng ở bên trái. (82642041)
Đã khắc phục sự cố xctrace export đôi khi xuất giá trị chuỗi UTF-16 thay vì UTF-8, dẫn đến lỗi kết xuất trong Terminal. (87594987)
Đã khắc phục sự cố trong đó một số sự kiện đã ghi sau khi khởi động lại sẽ bị mất khi nhập kho lưu trữ nhật ký trong quá trình khởi động lại thiết bị. (89003393)
Đã sửa lỗi xctrace list devices đôi khi không bao gồm tất cả các thiết bị được kết nối vật lý trong đầu ra của nó. (89142775) (FB9913864)
heap, leaks, and Instruments hiện nhận dạng chính xác bảng phụ của các đối tượng Swift đã hủy phân bổ dưới dạng bảng phụ. (89459805)
Đã khắc phục sự cố trong đó Công cụ không cho phép ghi các thiết bị tvOS được kết nối qua WiFi. (89592418) (FB9935783)
Đã khắc phục sự cố trong đó bản dựng gói Công cụ tùy chỉnh có thể không thành công khi cùng một biến được sử dụng nhiều lần trong trường biểu thức. (89653878)
heap, leaks, and Instruments hiện hiển thị tên dễ đọc hơn cho các loại thư viện tiêu chuẩn Foundation và Swift bổ sung. (90441836)
xctrace không còn trả về trạng thái lỗi khi truy tìm chỉ gây ra cảnh báo. Các vấn đề hiện được quy cho mức độ nghiêm trọng thích hợp. (90560207) (FB9963155)
Đã khắc phục sự cố trong xctrace export khi dữ liệu theo dõi VM và phân bổ đôi khi được xuất với các hàng dữ liệu bị thiếu. (90560947) (FB9963169)
Đã khắc phục sự cố trong đó Công cụ sẽ quên đối số dòng lệnh sau khi mở và đóng trình chỉnh sửa đích khi đã có đối số. (90666084)
Đã khắc phục sự cố khi di chuyển đầu kiểm tra trong thước theo dõi cũng sẽ cuộn trục Y của dòng thời gian. (90810506)
Đã sửa lỗi: Khoảng thời gian chạy vòng lặp cho thời gian chạy, lặp, chờ và bận có nhãn và độ dài không chính xác khi được hiển thị trong rãnh quy trình. (91130163)
Đã khắc phục sự cố Công cụ không liên tục khi ghi dấu vết với các mẫu Phân bổ hoặc Rò rỉ. (93916110)
Đã sửa lỗi: Tạo Báo cáo hiệu suất Core ML có thể không thành công sau khi bật Chế độ nhà phát triển trên thiết bị. (93923607)
Vấn đề đã biết
Ký hiệu mã người dùng dSYM có thể không thành công đối với tệp tailspin được nhập vào Công cụ. Các ký hiệu cho thư viện hệ thống cũng không xuất hiện khi các tệp này được tải. (93261223) Giải pháp thay thế : Ký hiệu nhật ký treo trong Terminal bằng spindump -i <UIKit-runloop*.ips>.
Vô hiệu hóa Trình kiểm tra hiệu suất luồng thông qua tab Chẩn đoán trong Trình chỉnh sửa lược đồ nếu Ứng dụng iOS hoặc macOS của bạn sử dụng Fishhook để kết nối các cuộc gọi tới libSystem nhằm mục đích gỡ lỗi/theo dõi. (94724380) (FB10131267)
Là một iOS developer, chắc hẳn mọi người đã từng làm những tính năng mà các UIKit của Apple không thể đáp ứng được yêu cầu. Lúc này chúng ta cần phải thực hiện custom hoặc tạo một common UI với cơ chế mới thoả mãn yêu cầu của khách hàng. Và ở dự án mình cũng có những yêu cầu như vậy, nên bài viết này mình sẽ hướng dẫn các bạn làm slider hai chiều trong Swift.
Mục đích của chúng ta là đạt được kết quả như sau:
Slider hai chiều
I. Yêu cầu
Cần tạo một common slider thoả mãn các điều kiện như sau:
Slider được chia thành các đoạn bằng nhau có thể chỉnh sửa, Slider có 2 điểm kéo, có thể kéo được từ giá trị min tới giá trị max, kéo chồng lên nhau, khi kéo tới các điểm sẽ thực hiện rung.
Cho phép thay đổi giao diện
II. Giải pháp
Tạo một mảng string để lưu các điểm cho phép và dùng nó để tính toán và hiển thị dữ liệu cho Slider hai chiều
Dựa vào số phần tử trong mảng để tính khoảng cách giữa các điểm và vị trí của nó.
Đối với tính năng kéo mình sẽ dùng UIPanGestureRecognizer để xử lí. Khi kết thúc hành động kéo thì xác định vị trí của điểm được kéo so với các điểm trong mảng, nếu gần điểm nào thì tự di chuyển tới vị trí của điểm đó.
III. Hướng dẫn chi tiết
Để cho tất cả mọi người đều có thể thực hiện một các dễ dàng thì mình sẽ hướng dẫn chi tiết theo các bước như sau:
1. Tạo mới file swift
Chúng ta cần tạo mới file swift để làm common Slider hai chiều theo các bước sau:
Trong project, bấm chuột phải vào thư mục muốn tạo file -> new file -> chọn swift -> đặt tên cho file, ở đây mình đặt là: TWSlider.swift
Kết quả tạo file
2. Thêm nội dung và logic cho file TWSlider.swift
Trước tiên chúng ta cần thêm image vào asset của project để sử dụng cho slider, các bạn cũng có thể thêm các image tương ứng với yêu cầu của dự án đang làm.
Và chúng ta cũng cần thêm Then vào để setup UI cho tiện như sau
public protocol Then {}
public extension Then {
@discardableResult
func then(_ block: (Self) -> Void) -> Self {
block(self)
return self
}
}
extension NSObject: Then {}
extension CGPoint: Then {}
extension CGRect: Then {}
extension CGSize: Then {}
extension CGPath: Then {}
1. Tạo protocol để bắn dữ liệu cần thiết ra màn hình cần xử lí logic
2. Tạo các biến cho phép thay đổi các giá trị của slider
class TWSlider: UIView {
// PUBLIC variable
// The number of points in slider
var numberOfSegments: Int = 0
// This value should be set if slider button should overlap or not. Default to NO. ie., 1 segment space will be present between the sliders.
var shouldSliderButtonOverlap: Bool = false
// The color that is used for rangeSlider unselected range view. (i.e., the view that is not within the slider points). Default is #DFDEE4.
var rangeSliderBackgroundColor: UIColor = .gray
// The color that is used for rangeSlider selected range view. (i.e., the view that is between the slider points). Default is #FF9800.
var rangeSliderForegroundColor: UIColor = .orange
// The label color to be used for min range display. Default is white.
var rangeDisplayLabelColor: UIColor = .white
// The label color to be used for min range display. Default is black.
var minMaxDisplayLabelColor: UIColor = .black
// The color for segment button when it is within the selected range. Default is clear.
var segmentSelectedColor: UIColor = .clear
// The color for segment button when it is outside the selected range. Default is #F8F8FA.
var segmentUnSelectedColor: UIColor = .white
// The image used for displaying slider buttons. By default "ic_sliderButton". rangeSliderButtonColor will be used if not set
var rangeSliderButtonImage: UIImage?
// The image for segment button when it is within the selected range. If not set, segmentSelectedColor will be used.
var segmentSelectedImage: UIImage?
// The image for segment button when it is outside the selected range. If not set, segmentUnSelectedColor will be used.
var segmentUnSelectedImage: UIImage?
// The size of the slider button. If not set, defaults to (16, 16).
var sliderSize: CGSize = CGSize(width: 16, height: 16)
// The size of the segments. If not set, defaults to (4, 4).
var segmentSize: CGSize = CGSize(width: 4, height: 4)
// The min and max range label text to be set by caller
var minRangeText: String?
var maxRangeText: String?
// The delegate property
weak var delegate: TWSliderDelegate?
// PRIVATE VAR
// Slider button size
private var SLIDER_BUTTON_WIDTH: CGFloat = 44.0
// Slider frame
private var DEFAULT_SLIDER_FRAME: CGRect!
// Segment width
private var segmentWidth: CGFloat!
// The backgroundView represent unselected/outside range view
private var sliderBackgroundView: UIView!
// The foregroundView represent selected/inside range view
private var sliderForegroundView: UIView!
// The label placed below the min and max sliders
private var minRangeView: UIView!
private var maxRangeView: UIView!
private var minRangeLabel: UILabel!
private var maxRangeLabel: UILabel!
// min and max value
private var minLabel: UILabel!
private var maxLabel: UILabel!
// Represent the range slider on either side of the slider
private var startSliderButton: UIButton!
private var endSliderButton: UIButton!
// The segment index or percent for initial slider position loading for segmented and unsegmented respectively
private var minRangeInitialIndex: Int?
private var maxRangeInitialIndex: Int?
// Padding range view with slider button
private let iconRangeMidView = "ic_rangeMidView"
private let iconRangeView = "ic_rangeView"
private let iconSliderButton = "ic_sliderButton"
private let iconSegmentUnSelected = "ic_segmentUnSelected"
private let paddingRangeView: CGFloat = 4
private var sliderMidView: UIView!
private var sliderMidLabel: UILabel!
private var checkInit: Bool = false
// MARK: init
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpUI()
}
override init(frame: CGRect) {
super.init(frame: frame)
setUpUI()
}
func setUpUI() {
setDefaultValues()
initSliderViews()
}
override func layoutSubviews() {
super.layoutSubviews()
updateFrame()
}
}
6. Tạo các hàm để tính toán vị trí và xử lí delegate của Pangesture
extension TWSlider {
private func addPanGestureRecognizer() {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture))
panGesture.maximumNumberOfTouches = 1
addGestureRecognizer(panGesture)
}
// Pan Gesture selector method
@objc
func handlePanGesture(_ panGesture: UIPanGestureRecognizer) {
let point = panGesture.location(in: self)
if panGesture.state == .began {
self.setSelectedStateForSlidingButton(point)
} else if panGesture.state == .changed {
sliderDidSlide(for: point)
} else if panGesture.state == .ended || panGesture.state == .failed || panGesture.state == .cancelled {
// Move the slider to nearest segment
moveSliderToNearestSegment(withEnding: point)
resetSelectedStateForSlidingButtons()
}
}
// If sliding began, check if startSlider is moved or endSlider is moved
private func setSelectedStateForSlidingButton(_ point: CGPoint) {
if startSliderButton.frame.contains(point) {
startSliderButton.isSelected = true
endSliderButton.isSelected = false
} else if endSliderButton.frame.contains(point) {
endSliderButton.isSelected = true
startSliderButton.isSelected = false
} else {
startSliderButton.isSelected = false
endSliderButton.isSelected = false
}
}
private func sliderDidSlide(for point: CGPoint) {
var newPoint = point
// Check if startButton is moved or endButton is moved. Based on the moved button, set the frame of the slider button and foregroundSliderView
newPoint = resetFrameOnBoundsCross(for: point)
if startSliderButton.isSelected {
if shouldStartButtonSlide(for: newPoint) {
UIView.animate(withDuration: 0.1, animations: {
// Change only the x value for startbutton
self.startSliderButton.frame = CGRect(x: self.sliderMidPoint(forPoint: newPoint.x), y: self.startSliderButton.frame.origin.y, width: self.startSliderButton.frame.size.width, height: self.startSliderButton.frame.size.height)
// Change the x and width for slider foreground view
if self.shouldSliderButtonOverlap {
let startMidX = self.startSliderButton.frame.midX
let endMidX = self.endSliderButton.frame.midX
var originX = self.startSliderButton.frame.origin.x
if endMidX < startMidX {
originX = self.endSliderButton.frame.origin.x
}
self.sliderForegroundView.frame = CGRect(x: originX + self.SLIDER_BUTTON_WIDTH / 2, y: self.sliderForegroundView.frame.origin.y, width: self.getSliderViewWidth(), height: self.sliderForegroundView.frame.size.height)
} else {
let originX = self.startSliderButton.frame.origin.x + self.SLIDER_BUTTON_WIDTH / 2
self.sliderForegroundView.frame = CGRect(x: originX, y: self.sliderForegroundView.frame.origin.y, width: self.getSliderViewWidth(), height: self.sliderForegroundView.frame.size.height)
}
// Change the x and width for slider foreground view
self.minRangeView.center = CGPoint(x: self.startSliderButton.frame.midX, y: self.startSliderButton.frame.minY - self.paddingRangeView)
self.sliderMidView.center = CGPoint(x: self.endSliderButton.frame.midX, y: self.startSliderButton.frame.minY - self.paddingRangeView)
// Update the intermediate segment colors
self.updateSegmentColor(for: newPoint)
}, completion: { _ in
self.callScrollDelegate(point: newPoint, isStartSliderButton: true)
})
}
} else if endSliderButton.isSelected {
if shouldEndButtonSlide(for: newPoint) {
UIView.animate(withDuration: 0.1, animations: {
// Change only the x value for endbutton
self.endSliderButton.frame = CGRect(x: self.sliderMidPoint(forPoint: newPoint.x), y: self.endSliderButton.frame.origin.y, width: self.endSliderButton.frame.size.width, height: self.endSliderButton.frame.size.height)
// Change the x and width for slider foreground view
if self.shouldSliderButtonOverlap {
let startMidX = self.startSliderButton.frame.midX
let endMidX = self.endSliderButton.frame.midX
var originX = self.startSliderButton.frame.origin.x
if endMidX < startMidX {
originX = self.endSliderButton.frame.origin.x
}
self.sliderForegroundView.frame = CGRect(x: originX + self.SLIDER_BUTTON_WIDTH / 2, y: self.sliderForegroundView.frame.origin.y, width: self.getSliderViewWidth(), height: self.sliderForegroundView.frame.size.height)
} else {
let originX = self.startSliderButton.frame.origin.x + self.SLIDER_BUTTON_WIDTH / 2
self.sliderForegroundView.frame = CGRect(x: originX, y: self.sliderForegroundView.frame.origin.y, width: self.getSliderViewWidth(), height: self.sliderForegroundView.frame.size.height)
}
// Change the x and width for slider foreground view
self.maxRangeView.center = CGPoint(x: self.endSliderButton.frame.midX, y: self.endSliderButton.frame.minY - self.paddingRangeView)
self.sliderMidView.center = CGPoint(x: self.startSliderButton.frame.midX, y: self.endSliderButton.frame.minY - self.paddingRangeView)
// Update the intermediate segment colors
self.updateSegmentColor(for: newPoint)
}, completion: { _ in
self.callScrollDelegate(point: newPoint, isStartSliderButton: false)
})
}
}
}
// Method that handles if the sliders move out of range
private func resetFrameOnBoundsCross(for point: CGPoint) -> CGPoint {
var newPoint = point
if shouldSliderButtonOverlap {
if point.x < 0 {
newPoint.x = 0
} else if sliderMidPoint(forPoint: point.x) >= sliderBackgroundView.bounds.maxX {
newPoint.x = sliderBackgroundView.bounds.maxX + SLIDER_BUTTON_WIDTH / 2
}
} else {
if startSliderButton.isSelected {
if sliderMidPoint(forPoint: point.x) >= endSliderButton.frame.midX - segmentWidth {
newPoint.x = endSliderButton.frame.midX - segmentWidth
} else if point.x < 0 {
newPoint.x = 0
}
} else if endSliderButton.isSelected {
if point.x <= startSliderButton.frame.midX + segmentWidth {
newPoint.x = startSliderButton.frame.midX + segmentWidth
} else if sliderMidPoint(forPoint: point.x) >= sliderBackgroundView.bounds.maxX {
newPoint.x = sliderBackgroundView.bounds.maxX + SLIDER_BUTTON_WIDTH / 2
}
}
}
return newPoint
}
private func shouldStartButtonSlide(for point: CGPoint) -> Bool {
if shouldSliderButtonOverlap {
return (point.x >= (SLIDER_BUTTON_WIDTH / 2)) && (point.x <= (bounds.maxX - SLIDER_BUTTON_WIDTH / 2))
} else {
var endButtonMidPoint = endSliderButton.frame.midX
endButtonMidPoint -= segmentWidth
return round(point.x) <= round(endButtonMidPoint) && point.x >= SLIDER_BUTTON_WIDTH / 2
}
}
private func shouldEndButtonSlide(for point: CGPoint) -> Bool {
if shouldSliderButtonOverlap {
return point.x >= SLIDER_BUTTON_WIDTH / 2
} else {
var startButtonMidPoint = startSliderButton.frame.midX
startButtonMidPoint += shouldSliderButtonOverlap ? 0 : segmentWidth
return round(point.x) >= round(startButtonMidPoint) && point.x <= sliderMidPoint(forPoint: frame.size.width)
}
}
// Call the delegate to set the label for min range and max range
private func callScrollDelegate(point: CGPoint, isStartSliderButton: Bool) {
let nearestSegmentIndex = Int(round(sliderMidPoint(forPoint: point.x) / segmentWidth))
var startIndex = Int(round(startSliderButton.frame.minX / segmentWidth))
var endIndex = Int(round(endSliderButton.frame.minX / segmentWidth))
if isStartSliderButton {
startIndex = nearestSegmentIndex
} else {
endIndex = nearestSegmentIndex
}
updateData(startIndex: startIndex, endIndex: endIndex, endDragDrop: false)
}
private func updateData(startIndex: Int, endIndex: Int, endDragDrop: Bool) {
if startIndex > endIndex {
let min = endIndex < 0 ? 0 : endIndex
let max = startIndex > numberOfSegments ? numberOfSegments : startIndex
delegate?.sliderScrolled(self, toMinIndex: min, andMaxIndex: max, endDragDrop: endDragDrop)
minRangeLabel.text = maxRangeText
maxRangeLabel.text = minRangeText
} else {
let min = startIndex < 0 ? 0 : startIndex
let max = endIndex > numberOfSegments ? numberOfSegments : endIndex
delegate?.sliderScrolled(self, toMinIndex: min, andMaxIndex: max, endDragDrop: endDragDrop)
minRangeLabel.text = minRangeText
maxRangeLabel.text = maxRangeText
}
if startIndex != endIndex {
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) {
self.sliderMidView.alpha = 0
self.minRangeView.alpha = 1
self.maxRangeView.alpha = 1
} completion: { _ in
print("completion \(startIndex) = \(endIndex)")
}
} else {
sliderMidLabel.text = "\(minRangeText ?? "") - \(maxRangeText ?? "")"
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseIn) {
self.sliderMidView.alpha = 1
self.minRangeView.alpha = 0
self.maxRangeView.alpha = 0
} completion: { _ in
print("completion \(startIndex) = \(endIndex)")
}
}
}
private func updateSegmentColor(for point: CGPoint) {
if shouldSliderButtonOverlap {
let startMinX = startSliderButton.frame.midX
let endMinX = endSliderButton.frame.midX
var min: CGFloat = 0.0
var max: CGFloat = 0.0
if startMinX > endMinX {
min = (endSliderButton.frame.minX / segmentWidth).rounded(.up)
max = (startSliderButton.frame.minX / segmentWidth).rounded(.down)
} else {
min = (startSliderButton.frame.minX / segmentWidth).rounded(.up)
max = (endSliderButton.frame.minX / segmentWidth).rounded(.down)
}
updateSegmentColor(withStart: Int(min) + 1, andEnd: Int(max) + 1)
} else {
if startSliderButton.isSelected {
let startMinX = (sliderMidPoint(forPoint: startSliderButton.frame.midX) / segmentWidth).rounded(.up)
let endMinX = (sliderMidPoint(forPoint: endSliderButton.frame.midX) / segmentWidth).rounded(.up)
updateSegmentColor(withStart: Int(startMinX) + 1, andEnd: Int(endMinX) + 1)
} else if endSliderButton.isSelected {
let startMinX = (startSliderButton.frame.minX / segmentWidth).rounded(.up)
let endMinX = (endSliderButton.frame.minX / segmentWidth).rounded(.down)
updateSegmentColor(withStart: Int(startMinX) + 1, andEnd: Int(endMinX) + 1)
}
}
let pointX: Int = Int(round(point.x - SLIDER_BUTTON_WIDTH / 2))
if pointX % Int(round(segmentWidth)) == 0 {
let generator = UIImpactFeedbackGenerator(style: .medium)
generator.impactOccurred()
}
}
private func updateSegmentColor(withStart startIndex: Int, andEnd endIndex: Int) {
// Segments before startSegment slider
if startIndex > 1 {
for segmentIndex in 1..<startIndex {
if let segmentButton = viewWithTag(segmentIndex) as? UIButton {
if let image = segmentUnSelectedImage {
segmentButton.setImage(image, for: .normal)
} else {
let image = getImageWithSize(segmentSize, with: segmentUnSelectedColor)
segmentButton.setImage(image, for: .normal)
}
}
}
}
// Segments between startSegment slider and endSegment slider
if startIndex <= endIndex {
for segmentIndex in startIndex...endIndex {
if let segmentButton = viewWithTag(segmentIndex) as? UIButton {
if let image = segmentSelectedImage {
segmentButton.setImage(image, for: .normal)
} else {
let image = getImageWithSize(segmentSize, with: segmentSelectedColor)
segmentButton.setImage(image, for: .normal)
}
}
}
}
// Segments after endSegment slider
if endIndex + 1 <= numberOfSegments {
for segmentIndex in (endIndex + 1)...numberOfSegments {
if let segmentButton = viewWithTag(segmentIndex) as? UIButton {
if let image = segmentUnSelectedImage {
segmentButton.setImage(image, for: .normal)
} else {
let image = getImageWithSize(segmentSize, with: segmentUnSelectedColor)
segmentButton.setImage(image, for: .normal)
}
}
}
}
}
// Slide to nearest position
private func moveSliderToNearestSegment(withEnding point: CGPoint) {
var newPoint = point
newPoint = resetFrameOnBoundsCross(for: point)
let nearestSegmentIndex = Int(round(sliderMidPoint(forPoint: newPoint.x) / segmentWidth))
sliderDidSlide(for: CGPoint(x: CGFloat(SLIDER_BUTTON_WIDTH / 2 + CGFloat(nearestSegmentIndex) * segmentWidth), y: newPoint.y))
var startIndex = Int(round(startSliderButton.frame.minX / segmentWidth))
var endIndex = Int(round(endSliderButton.frame.minX / segmentWidth))
if startSliderButton.isSelected {
startIndex = nearestSegmentIndex
} else if endSliderButton.isSelected {
endIndex = nearestSegmentIndex
}
updateData(startIndex: startIndex, endIndex: endIndex, endDragDrop: true)
}
// After ending, reset the selected state of both buttons
private func resetSelectedStateForSlidingButtons() {
startSliderButton.isSelected = false
endSliderButton.isSelected = false
}
}
5. Tạo các hàm public cho phép thay đổi giá trị của TWSlider
extension TWSlider {
// Scroll to desired location on loading
func scrollStartSlider(to startIndex: Int, andEnd endIndex: Int) {
minRangeInitialIndex = startIndex
maxRangeInitialIndex = endIndex
if checkInit {
slideRangeSliderButtonsIfNeeded()
}
}
// MARK: - Setter methods, setup value for slider
func setNumberOfSegments(_ numberOfSegments: Int, minText: String?, maxText: String?) {
self.numberOfSegments = numberOfSegments
// After setting the numberOfSegments, set all the necessary views
segmentWidth = getSegmentWidth(forSegmentCount: self.numberOfSegments)
addSegmentButtons()
addSubview(startSliderButton)
addSubview(endSliderButton)
minLabel.text = minText
maxLabel.text = maxText
}
private func addSegmentButtons() {
for segmentIndex in 1...numberOfSegments {
let segmentButton = getSegmentButton(withSegmentIndex: segmentIndex, isSlider: false)
segmentButton.then {
$0.tag = segmentIndex
$0.isUserInteractionEnabled = false
addSubview($0)
}
}
}
}
3. Cách sử dụng
Bước 1: mở file storyboard hoặc file .xib và thêm UIView và constraint, sửa lại tên class thành class custom slider mới tạo ở trên ở project này là TWSlider -> Kéo outlet
Bước 2: mở viewcontroller vừa được kéo outlet viết hàm setup cho slider như sau
Vậy là chúng ta đã hoàn thành việc custom Slider hai chiều, mình hi vọng nó có thể giải quyết được bài toán của các bạn. Cảm ơn các bạn đã theo dõi bài viết!
Như mọi người đã biết, tất cả các ứng dụng hiện nay đều có những ô nhập (TextField, TextView …) để hỗ trợ người dùng điền thông tin của họ và gửi về phía server để xử lí. Khi này để giảm tải công việc cho server chúng ta cần phải kiểm tra dữ liệu để loại bỏ bớt các trường hợp dữ liệu không đúng trước khi gửi lên server. Để làm việc này thì thông thường chúng ta sẽ nghĩ ngay đến Regex. Vậy Regex là gì? Áp dụng Regex vào source code Swift như nào? mời các bạn theo dõi tiếp bài viết của mình nhé.
Regex là gì?
Regex là viết tắt của Regular Expression, tiếng việt được gọi là Biểu Thức Chính Quy. Regex là một công cụ rất mạnh trong việc xử lí chuỗi, nó thường được dùng để kiểm tra tính hợp lệ của một chuỗi hay tìm kiếm, vì vậy chúng ta nên trang bị cho mình một số kiến thức cơ bản về Regex để có thể xử lí công việc nhẹ nhàng và nhàn hạ hơn.
Nhược điểm của Regex
Công nhận regex là một công cụ rất mạnh trong việc xử lí chuỗi, tuy nhiên nó cũng có một số nhược điểm như sau:
Khó đọc
Nhìn vào đoạn Regex: ^([A-Za-z0-9!@#$%^&*?]{8,})$
Khi mới bắt đầu chúng ta sẽ rất khó để biết được regex này đang làm nhiệm vụ gì. Vì nó được mô tả bới các kí tự không quen thuộc với ngôn ngữ chúng ta hay dùng.
Dễ quên
Do nó khó đọc, khó hiểu nên nó cũng khiến chúng ta dễ quên, vì vậy mỗi lần cần đến 1 regex nào là chúng ta thường phải tìm đến google để trợ giúp. Để khắc phục nhược điểm này mỗi khi chúng ta làm việc mới một regex khó hoặc dài thì chúng ta sẽ lưu lại vào 1 file lưu trữ, để sau này dùng đến tìm lại cho nhanh.
Regex dùng để làm gì?
Regex thường được dùng để tìm kiếm, để tách chuỗi, để kiểm tra chuỗi, …
1. Dùng để kiểm tra chuỗi
Ví dụ ta cần kiểm tra độ phức tạp của mật khẩu với yêu cầu như sau:
Phải có ít nhất một kí tự viết thường Regex: [a-z]: Sẽ khớp với bất kì kí tự viết thường nào trong chuỗi(a-z) Input: AAA -> FALSE Input: aabc hoặc Aza -> TRUE
Phải có ít nhất một kí tự viết hoa Regex: [A-Z] : Sẽ khớp với bất kì kí tự viết hoa nào trong chuỗi(A-Z) Input: AAA hoặc Abc -> TRUE Input bbca -> FALSE
Phải có ít nhất một kí tự số Regex: [0-9] : Sẽ khớp với bất kì kí tự số nào trong chuỗi(0-9) Input: 1234 hoặc A0bc -> TRUE Input bbca -> FALSE
Phải có ít nhất một trong số kí tự đặc biệt sau @!#$ Regex: [@!#$] : Sẽ khớp với bất kì kí tự số nào trong chuỗi(@!#$) Input: 12@4 hoặc A!bc -> TRUE Input bbca -> FALSE
Không được có dấu cách Regex: [ ] : Sẽ khớp với bất kì kí tự nào trong chuỗi là space Input: 12 4 hoặc A bc -> TRUE Input bbca -> FALSE
2. Dùng để tách chuỗi
Ví dụ chúng ta có một chuỗi kí tự như sau: ABCxyz123mM
Tách lấy hết các kí tự viết hoa từ chuỗi trên ta sử dụng regex: [A-Z], ta sẽ thu được kết quả từ chuỗi ABCxyz123mM -> ABCM
Tách lấy hết kí tự viết thường ta dùng regex: [a-z], ta thu được kết quả như sau: ABCxyz123mM -> abcm
Tách lấy hết kí tự số ta thường dùng regex: [0-9], Ta thu được kết quả như sau: ABCxyz123mM -> 123
3. Dùng để tìm kiếm
Regex khá là phổ biến, nó có mặt trên hầu hết các IDE hiện nay. xCode, Android studio, Subline text, note pad ++, …
Ví dụ phần mềm Subline text
Để kích hoạt tính năng này các bạn bấm command + f (tổ hợp tìm kiếm) và chọn mục regex (.*)
Ảnh dưới đây mình có viết 1 đoạn regex: ^N.*o$ để tìm ra chuỗi bắt đầu bằng kí tự N và có kết thúc bằng kí tự o
Giải thích ý nghĩa các kí tự để viết Regex
Sau đây mình sẽ giải thích kĩ hơn về các kí tự để viết regex và cách sử dụng chúng.
1.Kí tự thường
Mã Regex
Mô tả
Ghi chú
a|b
Khớp với a hoặc b
[0-9]
Khớp với các kí tự là số 0,1,2..9
[a-z]
Khớp với các kí tự viết thường từ a tới z
Nếu đổi z thành c thì sẽ là từ a tới c
[ABC]
Khớp với các kí tự là ABC
[^ABC]
Khớp với các kí tự không phải là ABC
Nếu dấu ^ xuất hiện phía trong [] có nghĩa nó là Phủ định của tập hợp đó
\d
Khớp với số bất kì
thay thế cho [0-9]
\D
Khớp với các kí tự không phải số
Phủ định của \d
\s
Khớp với tất cả kí tự là khoảng trằng, tab, hoặc xuống dòng
\S
Khớp với tất cả các kí tự không phải là khoảng trắng, tab hoặc xuống dòng
Phủ định của \s
\S+
Khớp với một hoặc nhiều kí tự không phải là khoảng trắng, tab hoặc xuống dòng
\w
Khớp với bất kì ký tự chữ
Thay thế cho [a-zA-Z0-9]
\W
Khớp với kí tự bất kì không phải chữ
phủ định của \w
\b
Khớp khi kí tự trước đó nằm ở cuối chuỗi
regex: dao\b minhdao: true daominh: false
\B
Phủ định của \b
2. Kí tự đặc biệt
Regex
Mô tả
Ghi chú
.
Khớp với tất cả các kí tự trừ kí tự xuống dòng
Thay thế cho [^\n\r]
^
Bắt đầu
regex: ^dao daominh: True minhdao: False
$
Kết thúc
regex: dao$ daominh: False minhdao: True
|
Điều kiện hoặc
a|b a: true b: true c: false
\
Biến 1 kí tự đặc biệt thành kí tự thường hoặc ngược lại
d: kí tự d thông thường \d: khớp với kí tự số bất kì
3. Lặp
Regex
Mô tả
Ghi chú
*
Xuất hiện 0 hoặc nhiều lần
Tương đương {0,}
+
Xuất hiện 1 hoặc nhiều lần
Tương đương {1,}
?
Xuất hiện 0 hoặc 1 lần
Tương đương {0,1}
{x,y}
Xuất hiện từ x lần tới y lần
{3}: xuất hiện đúng 3 lần {3,}: xuất hiện từ 3 lần hoặc nhiều hơn {3, 10} Xuất hiện từ 3 lần đến 10 lần
4. Nhóm
Regex
Mô tả
Ghi chú
()
Nhóm nhiều mã lại với nhau tạo thành nhóm điều kiện
(?:x)
Khớp với x nhưng không nhớ kết quả khớp
x(?=y)
Chỉ khớp x nếu ngay sau x là y
x(?!y)
chỉ khớp x nếu ngay sau x không phải y
Ứng dụng regex vào việc kiểm tra dữ liệu UITextField bằng ngôn ngữ swift
Để ứng dụng được regex vào việc kiểm tra dữ liệu ở swift chúng ta cần biết một chút kiến thức cơ bản về regex(mình đã giới thiệu ở phía trên) và hiểu rõ về cách hoạt động của TextField
Các hàm common cần dùng
extension String {
/// check string is match regex or not
/// - Parameter regex: regular expression
/// - Returns: true if match
func isMatches(_ regex: String) -> Bool {
do {
let regex = try NSRegularExpression(pattern: regex)
let matches = regex.matches(in: self, range: NSRange(location: 0, length: self.count))
return !matches.isEmpty
} catch {
print("Something went wrong! Error: \(error.localizedDescription)")
}
return false
}
/// Return string matchs regex
/// - Parameter regex: regular expression
/// - Returns: all string match regex
func filter(regex: String) -> String {
do {
let regex = try NSRegularExpression(pattern: regex)
let results = regex.matches(in: self, range: NSRange(self.startIndex..., in: self))
return results.map { String(self[Range($0.range, in: self)!]) }.joined(separator: "")
} catch {
print("Something went wrong! Error: \(error.localizedDescription)")
}
return ""
}
/// Remove mark in string
var folded: String {
self.folding(options: .diacriticInsensitive, locale: nil)
.replacingOccurrences(of: "đ", with: "d")
.replacingOccurrences(of: "Đ", with: "D")
}
}
func isMatches(_ regex: String) -> Bool: hàm này dùng để kiểm tra xem chuỗi có khớp với regex hay không?
func filter(regex: String) -> String: Hàm này dùng để lấy ra tất cả các kí tự thoả mãn điều kiện của regex
var folded: String : giúp biến đổi các kí tự có dấu về không dấu VD: Đ -> D, ê -> e …
extension UITextField {
/// validate input data
/// - Parameters:
/// - maxLength: max char of text field
/// - range: location and lenth of current selected text
/// - string: new string will be replacce at range
func validateInput(maxLength: Int, range: NSRange, string: String) {
guard let textFieldText = self.text else {
return
}
if self.text.safeValue.count == maxLength, !string.isEmpty {
return
}
let text: NSString = textFieldText as NSString
let finalString: String = text.replacingCharacters(in: range, with: string)
let newString = String(finalString.prefix(min(maxLength, finalString.count)))
self.text = newString
let countChange: Int = newString.count - textFieldText.count
let validateCount: Int = countChange > 0 ? countChange : 0
self.setCursorPosition(range.location + validateCount)
}
/// Set Cursor position
/// - Parameter cursorPosition: Int
func setCursorPosition(_ cursorPosition: Int) {
guard cursorPosition <= (self.text ?? "").count,
let posCursor = self.position(from: beginningOfDocument, offset: cursorPosition) else { return }
DispatchQueue.main.async {
self.selectedTextRange = self.textRange(from: posCursor, to: posCursor)
}
}
}
func validateInput(maxLength: Int, range: NSRange, string: String): Dùng khi cần tự validate các kí tự cho phép, hoặc chặn, hoặc copy paste chuỗi, rồi set lại giá trị cho text field.
func setCursorPosition(_ cursorPosition: Int): Dùng để di chuyển con trỏ khi copy paste.
Hiểu rõ các func trong UITextField delegate hoạt động
Ở đây mình chỉ nói đến các delegate hay dùng làm nhiệm vụ kiểm tra dữ liệu
func textFieldDidEndEditing(_ textField: UITextField): Dùng để kiểm tra dữ liệu khi người dùng focus out, thường được dùng khi muốn kiểm tra dữ liệu tại thời điểm người dùng kết thúc thao tác nhập dữ liệu.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool : thường được dùng khi muốn kiểm tra dữ liệu tại thời điểm người dùng nhập(realtime), dùng để chặn những kí tự không cho phép. – textField: đây là text field đang được người dùng tương tác – range: Vị trí và độ dài mã text sắp được thay đổi – chuỗi được thay thế tại range NOTE: Khi replacement string = empty có nghĩa là người dùng đang thực hiện thao tác xoá hoặc dùng gợi ý nội dung hoặc keychain của bàn phím.
event Editting Changed: Dùng khi không chặn kí tự ở hàm 2(shouldChangeCharacterIn), event này chạy khi có thay đổi kí tự trong text field, với điều kiện shouldCHangeCharacter phải return true
Sử dụng regex để kiểm tra và lọc realtime kí tự nhập vào UITextField
Từ các common code phía trên để kiểm tra chuỗi mà người dùng nhập vào chúng ta sẽ thực hiện nó trong hàm shouldChangeCharacter in range
Ví dụ: Text field chỉ cho nhập các kí tự chữ hoa chữ thường và không cho nhập tiếng việt
Nếu muốn khi copy paste một chuỗi có dấu tự convert về chuỗi không dấu ta làm như sau: Sử dụng folded để convert chuỗi về chuỗi không dấu trước khi filter nó với regex
Tương tự với từng yêu cầu chúng ta sẽ tạo ra các regex khác nhau và đưa vào sử dụng một cách dễ dàng và hiệu quả
Sử dụng Regex để kiểm tra khi người dùng kết thúc nhập liệu
Nếu bạn muốn kiểm tra chuỗi người dùng vừa xong có đúng định dạng email hay không thì ta sử dụng hàm textFieldDidEndEditing khi người dùng kết thúc nhập để kiểm tra như sau:
Để tránh sai sót khi sử dụng các regex ở nhiều chỗ khác nhau trong project, chúng ta nên tạo ra enum để quản lí các chuỗi regex này như sau:
enum Regex: String {
case none = "[\\s\\S]"
case min8CharNoSpace = "^([A-Za-z0-9!@#$%^&*?]{8,})$"
case haveCharAndNumber = "(?=.*[a-zA-Z])(?=.*[0-9])"
case haveUpercaseAndLowerCase = "(?=.*[a-z])(?=.*[A-Z])"
case haveSpecialChar = "[!@#$%^&*?]"
case vietnamese = "^[a-zA-Z0-9ÀÁÂÃÈÉÊÌÍÒÓÔÕÙÚĂĐĨŨƠàáâãèéêìíòóôõùúăđĩũơƯĂẠẢẤẦẨẪẬẮẰẲẴẶẸẺẼỀỀỂẾưăạảấầẩẫậắằẳẵặẹẻẽềềểếỄỆỈỊỌỎỐỒỔỖỘỚỜỞỠỢỤỦỨỪễệỉịọỏốồổỗộớờởỡợụủứừỬỮỰỲỴÝỶỸửữựỳỵỷỹ\\s]+$"
case email = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$"
case emailInput = "[a-zA-Z0-9.@]"
case password = "[a-zA-Z0-9!@#$%^&*?]"
var maxLength: Int {
switch self {
case .none:
return .max
case .min8CharNoSpace:
return 100
case .haveCharAndNumber:
return 100
case .haveUpercaseAndLowerCase:
return 100
case .haveSpecialChar:
return 100
case .vietnamese:
return 100
case .email:
return 100
case .emailInput:
return 100
case .password:
return 30
}
}
}
Mình hi vọng bài viết này giúp mọi người hiểu rõ hơn về regex và cách ứng dụng nó vào trong công việc. Chúc các bạn thành công!
Như mọi người đã biết, hầu hết các ứng dụng ngày nay đều hỗ trợ tính năng đa ngôn ngữ, nhằm mục đích tiếp cận được nhiều người dùng hơn, cho mọi người sử dụng ứng dụng dễ dàng hơn. Tuy nhiên hiện nay hầu hết các dự án đều làm một cách tự phát, chưa có một quy trình chuẩn. Điều này làm cho quá trình phát triển sinh ra nhiều bug UI sai Message, sai chính tả, nó làm tốn thời gian không đáng có của các bên.
Trong quá trình làm việc ở các dự án, khi tài liệu được cập nhật đồng nghĩa với việc các Dev phải quay lại kiểm tra các thay đổi message, rồi bắt đầu loay hoay tìm kiếm trong source code để thực hiện thay đổi.
Hay mỗi khi code một màn hình mới việc phải định nghĩa một đống key, định nghĩa enum/constant và copy/paste nó sang các file localization thật nhàm chán và tiềm tàng nhiều rủi ro về lỗi sai chính tả, copy nhầm. Trước đây, có một member trong dự án của mình chỉ vì copy sai text làm thừa một dấu “;” làm lỗi cả file đa ngôn ngữ, việc này làm cho toàn bộ text trên ứng dụng khi release cho bị sai. Do file đa ngôn ngữ là string nên những lỗi sai chính tả như này mất rất nhiều thời gian để điều tra lỗi.
Sau nhiều năm làm việc với các dự án khác nhau, cùng các anh em, cộng sự hoàn thành các ứng dụng to nhỏ khác nhau. Mình đã đúc kết được một quy trình giúp cải thiện năng suất làm việc của mọi người, giúp giảm các bug UI về sai message, sai chính tả, giảm thời gian thực hiện tính năng đa ngôn ngữ, giúp quá trình bảo trì, thay đổi message trở nên dễ dàng hơn.
Quy trình
Brainstoming
Trước tiên, để đạt được hiệu quả Leader của các bên gồm BA, Mobile, Web … ngồi lại với nhau để thống nhất về cách làm việc theo quy trình trên. Sau đó phổ biến lại cho member và đảm bảo việc member thực hiện đúng quy trình làm việc.
Tạo file excel chung lưu các message cần làm đa ngôn ngữ
Trong file này mình có định nghĩa sẵn các trường cần nhập và có sẵn công thức để gen ra code của swift và android. Nếu dự án của các bạn sử dụng ngôn ngữ khác thì có thể chỉnh lại công thức cho phù hợp với dự án của mình.
Nhập nội dung
Thêm/sửa message
Screen/Feature: Điền vào tiên màn hình hoặc tính năng.
MessageKey: Điền vào tên item trên màn hình, nếu SRS có mô tả thì ưu tiên sử dụng key trong SRS, nếu không hãy đặt tên sao cho đúng ý nghĩa của item.
Tiếng việt: Text được hiển thị khi ngôn ngữ là Tiếng Việt
Tiếng anh: Text được hiển thị khi ngôn ngữ là Tiếng Anh
Note: TH1. Tài liệu đã định nghĩa sẵn các message cho các ngôn ngữ: thì BA có thể cử 1 bạn ra điền hết vào file này trước khi bên dev thực hiện code. Trường hợp này thì việc làm đa ngôn ngữ khá nhàn, mình sẽ giải thích chi tiết ở các phần phía dưới. TH2: Tài liệu chưa định nghĩa message cho các ngôn ngữ(các dự án thường rơi vào trường hợp này): Khi này khi các Dev thực hiện code sẽ tự insert thêm message vào file trên teams, để file tự tạo ra code tương ứng với các ngôn ngữ.
Công thức tạo code tự động
Công thức tạo code từ excel
Các Developers sẽ không phải làm các thao tác lặp đi lặp lại nhàm chán. Ngoài ra nó giúp giảm lỗi đánh máy, lỗi copy paste, lỗi sai chính tả và giúp việc làm đa ngôn ngữ nhanh hơn. Trên file excel mình có tạo ra công thức để tự tạo ra code của swift/android. Khi bạn điền thông tin vào sheet MessageList thì code sẽ được gen tự động, việc của mọi người là chỉ cần copy code vào project và sử dụng.
Đưa ra các luật lệ
Tạo ra các luật yêu cầu member phải tuân thủ như sau:
Đặt tên màn hình theo quy định của dự án, cách đặt tên cần thống nhất giữa tài liệu SRS và các class trong code của Dev.
Dev của các bên(mobile, web) và BA khi thực hiện thêm mới, hay chỉnh sửa sẽ thực hiện trực tiếp trên files Localize chung của dự án, khi chỉnh sửa cần tìm đúng tên màn hình, message để sửa, nếu thêm mới thì thêm xuống cuối cùng của của danh sách list message của màn hình đó.
Để tránh việc conflict hoặc giẫm chân nhau khi các bên chạy song song, thì mỗi màn hình những member liên quan tới màn hình đó sẽ cử ra một bạn điển vào file message trước.
Cách thực hiện code
TH1: đối với dự án không cho sử dụng thư viện mã nguồn mở
Khi thực hiện chúng ta chỉ cần vào file excel và copy code vào project của mình rồi sử dụng bình thường.
TH2:Dự án cho phép sử dụng thư viện mã nguồn mở
Chúng ta sẽ sử dụng thư viện để tự tạo ra enum nhanh gọn lẹ hơn, ví dụ bên iOS Swift thì chúng ta có thể sử dụng Swiftgen để thực hiện. Link hướng dẫn sử dụng Swiftgen mình cũng có viết môt bài rồi các bạn có thể tham khảo link dưới đây:
Giảm thời gian làm việc, tăng năng suất làm việc của team
Giảm khả năng bị lỗi UI/Message hay các lỗi về copy/paste khi thực hiện
Giảm thời gian bảo trì ứng dụng khi thay đổi message, khi có thay đổi BA sẽ update file message list và báo lại cho Devs, lúc này Dev không cần phải tìm xem thay đổi ở đâu, mà chỉ cần copy code từ file message list là xong.
Giảm thời gian implement khi có yêu cầu hỗ trợ thêm ngôn ngữ khác. Vì khi này chúng ta chỉ cần thêm 1 cột nữa ở file excel rồi copy code sang project là xong.
Giúp tối ưu được effort khi các team không start cùng một thời điểm. Vì team start sau sẽ sử dụng lại được phần đã làm của team start trước.
Nhược điểm
Cần quản lí, phân chia công việc tốt, member có khả năng làm việc nhóm.
Thường chỉ hiệu quả cao đối với các dự án mới
Tổng kết
Phía trên là nội dung chia sẻ về cách tối ưu khi làm ứng dụng hỗ trợ đa ngôn ngữ, mình hi vọng bài viết có thể giúp mọi người phần nào trong quá trình thực hiện các ứng dụng có hỗ trợ đa ngôn ngữ. Cảm ơn mọi người đã dành thời gian đọc bài viết này.
Tìm hiểu cách mà SwiftGen giúp dễ dàng loại bỏ các chuỗi ma thuật(magic strings) trong các dự án iOS của bạn.
Là một nhà phát triển ứng dụng trên thiết bị di động, bạn có thể cảm thấy như bạn gặp phép thuật hàng ngày. Các dòng mã bạn viết sẽ được chuyển đổi thành các ứng dụng mà mọi người trên khắp thế giới có thể sử dụng. Các công cụ mà Apple cung cấp giúp biến điều kỳ diệu đó thành hiện thực và làm cho cuộc sống của bạn dễ dàng hơn. Khi tiến xa hơn vào lĩnh vực phát triển phần mềm, bạn có thể nhận ra có một thứ ma thuật mà bạn không thích: Chuỗi thần kỳ(magic strings).
Loại an toàn, khái niệm rằng các biến chỉ có thể thuộc một loại cụ thể, cung cấp cho các nhà phát triển các rào chắn(guardrails) để giữ cho các chương trình của họ an toàn. Tuy nhiên, chuỗi ma thuật đưa mã không an toàn vào các ứng dụng đó. Chuỗi ma thuật là gì? Trong quá trình phát triển iOS, bạn đã gặp phải những điều này nhiều lần. Một ví dụ trông giống như sau:
let color = UIColor(named: "green-apple")
self.title = "Welcome!"
Ví dụ này hiển thị “green-apple” và “Welcome!” được viết dưới dạng chuỗi trực tiếp trong mã của bạn. Không quá khi nói rằng tất cả các nhà phát triển đôi khi nhận thấy mình có lỗi với hành vi này.
Trên thực tế, trong quá trình phát triển iOS, bạn không có nhiều lựa chọn. Ngoài ra, Xcode không cung cấp cách nào để tránh việc này.
Những người đã làm việc trong Android có thể thấy mình run sợ trước những đoạn mã như thế này. Môi trường phát triển Android có cơ chế chuyển đổi tài nguyên ứng dụng, chẳng hạn như chuỗi, màu sắc, hình ảnh và phông chữ, thành các kiểu biến an toàn. Có rất nhiều lợi ích từ việc này, và đó là:
Giảm lỗi sai chính tả
Ngăn chặn sự trùng lặp tài nguyên không cần thiết
Cung cấp kiểm tra tài nguyên tại thời điểm biên dịch
Giúp dọn dẹp tài nguyên cũ
Và nhiều hơn thế nữa
Như đã nêu, các nhà phát triển iOS và macOS không có quyền truy cập vào hệ thống cung cấp sự an toàn cho loại tài nguyên này.
May mắn thay, có SwiftGen, một trình tạo mã để loại bỏ các chuỗi ma thuật trong ứng dụng của bạn. Có sẵn dưới dạng thư viện mã nguồn mở trên GitHub, bạn có thể thêm tính năng này vào các dự án iOS và macOS của mình để mang lại sự an toàn về loại và kiểm tra thời gian biên dịch của tất cả các Assets của bạn.
Trong hướng dẫn này bạn sẽ học được cách:
Thiết lập dự án của bạn với SwiftGen
Xác định các nội dung mà bạn muốn chuyển đổi
Xác định nơi mà code được generated
Tạo các templates cho phép SwiftGen generate khi dùng SwiftUI cho Fonts và màu sắc.
Getting Started
Để bắt đầu, hãy nhấp vào nút Tải xuống tài liệu dưới đây:
Có một số cách bạn có thể cài đặt SwiftGen để hoạt động với môi trường của mình như sau:
CocoaPods
Homebrew
Mint
Directly download a zipped release(Nên dùng cách này)
Link hướng dẫn cài ở đây: https://github.com/SwiftGen/SwiftGen
Trong hướng dẫn này, bạn sẽ sử dụng CocoaPods để quản lý SwiftGen.
Lưu ý: Nếu bạn không có CocoaPods, đừng lo lắng – dự án khởi động và dự án cuối cùng đã được tải xuống phần phụ thuộc. :]
Mở workspace, có tên là DrinksUp! .Xcworkspace. Vì dự án này sử dụng CocoaPods nên bạn sẽ không thể làm việc trực tiếp với DrinksUp! .Xcodeproj.
Hãy dành một chút thời gian để xem xét xung quanh trong Xcode. Dự án đã ở trạng thái hoàn thành nhưng sử dụng chuỗi để tham chiếu phông chữ, màu sắc, hình ảnh và chuỗi. Bạn sẽ chuyển đổi tất cả những thứ này vào cuối hướng dẫn.
Xây dựng và chạy và làm quen với ứng dụng.
Ứng dụng, DrinksUp !, là một cách để theo dõi các loại đồ uống thú vị mà bạn và gia đình đã thử khi đến nhà hàng hoặc ở nhà.
Thiết lập SwiftGen
Bắt đầu bằng cách mở Terminal và điều hướng đến thư mục gốc của dự án khởi động của bạn. Tiếp theo, nhập lệnh sau vào Terminal:
./Pods/SwiftGen/bin/swiftgen config init
Thao tác này sẽ tạo một tệp cấu hình, có tên là swiftgen.yml, tại thư mục gốc dự án của bạn. Nếu tệp này tự động mở trong Xcode, hãy tiếp tục và tắt nó đi.
Tiếp theo, trong workspace dự án của bạn, đi tới Files ▸ Add new file vào “DrinksUp!”…. Tìm swiftgen.yml. Đảm bảo bỏ chọn Sao chép các mục nếu cần và chọn Tạo tham chiếu thư mục.
Nhấp vào nút Thêm. Khi hoàn tất, bạn sẽ thấy swiftgen.yml ở đầu trình điều hướng Dự án, như bên dưới:
Lưu ý: Bạn có thể di chuyển tệp này đến vị trí giống như hình minh họa, nếu Xcode không thêm nó theo cách tương tự.
Tệp này là nơi bạn sẽ đặt các hướng dẫn cho SwiftGen biết tệp nào bạn muốn chuyển đổi thành mã được tạo. Loại tệp, YML , cho biết nó đang sử dụng YAML cho cú pháp của nó. Nếu bạn chưa sử dụng YAML trước đây, đây chỉ đơn giản là một cách dễ đọc hơn để xem dữ liệu được tuần tự hóa. Bạn có thể nghĩ về nó như là JSON , được đơn giản hóa.
Bây giờ, thay thế toàn bộ nội dung của swiftgen.yml bằng nội dung sau:
Bạn đã khai báo một biến input_dir thư mục đầu vào. Điều này cho SwiftGen biết thư mục gốc để điều hướng đến tất cả các đường dẫn tệp mà bạn sẽ sớm thêm vào.
Một biến khác xác định thư mục đầu ra của các tệp Swift được tạo. Bằng cách này, bạn sẽ dễ dàng theo dõi tất cả các tệp SwiftGen hơn.
Thêm Build Phase
Để chạy SwiftGen, bạn sẽ cần thêm một giai đoạn xây dựng mới vào dự án của mình. Để thực hiện việc này, hãy chọn dự án của bạn trong trình điều hướng Dự án, chọnBuild Phases. Chọn + và chọnNew Run Script Phase.
Đổi tên script thành SwiftGen bằng cách nhấp đúp vào tên hiện tại, Run Script . Tiếp theo, thêm phần sau vào trường văn bản của tập lệnh:
if [[ -f "${PODS_ROOT}/SwiftGen/bin/swiftgen" ]]; then
"${PODS_ROOT}/SwiftGen/bin/swiftgen"
else
echo "warning: SwiftGen is not installed. Run 'pod install --repo-update' to install it."
fi
Cuối cùng, sắp xếp lại thứ tự tập lệnh để ngồi ngay sau tên tập lệnh [CP] Check Pods Manifest.lock . Các Build Phases của bạn bây giờ sẽ giống như sau:
Build và Run. Nếu mọi thứ được thiết lập đúng cách, bạn sẽ không có bất kỳ lỗi nào. Bạn sẽ không có bất kỳ thứ gì trong thư mục Đã tạo của mình. Được rồi chúng ta đến bước tiếp theo.
Chuyển đổi XCAssets
Bây giờ, bạn đã sẵn sàng để bắt đầu xóa các chuỗi khỏi dự án của mình! Bước đầu tiên sẽ là SwiftGen tạo mã cho các tệp XCAsset trong dự án. Mở swiftgen.yml và thêm phần sau vào cuối tệp:
Đây là ý nghĩa của mỗi dòng trong số những dòng này:
Mỗi loại tệp hoặc mẫu, bạn muốn chuyển đổi bằng SwiftGen yêu cầu một mục nhập ở cấp gốc là swiftgen.yml . Ở đây, điều này cho biết bạn muốn SwiftGen chuyển đổi các tệp là XCAsset .
Danh sách này cho biết những tệp SwiftGen nên giới hạn chuyển đổi của nó.
Bạn cần cho SwiftGen biết cách tạo đầu ra.
Bạn phải cung cấp tên mẫu. Ở đây, swift5 là một mẫu mặc định được cung cấp bởi nhóm SwiftGen. Bạn sẽ học cách sử dụng các mẫu của riêng mình sau này.
Cuối cùng, bạn cung cấp tên tệp mà bạn muốn mã Swift mới của mình tạo ra. Hãy nhớ rằng, bạn đã xác định output_dir ở đầu tệp, có nghĩa là nó sẽ xuất thành Generated / XCAssets + Generated.swift .
Build và Run. Nếu bạn không gặp bất kỳ lỗi nào, thì quá trình tạo mã của bạn đã hoạt động!
Thêm tệp
Mở rộng folder Generated trong trình Project navigator. Hiện tại, bạn vẫn sẽ không tìm thấy tệp mới của mình. Để thêm nó, nhấp chuột phải vào Generated và chọn Add Files to “DrinksUp!”… .
Chọn XCAssets + Generated.swift . Đảm bảo Copy items if needed không được chọn, sau đó nhấp vào Add . Bây giờ, hãy mở XCAssets + Generate.swift và quan sát. Bạn sẽ thấy enumAsset. Trong enum, bạn sẽ tìm thấy các bảng liệt kê khác được xác định phù hợp với các danh mục XCAsset mà bạn đã xác định. Ví dụ:
Assets: Mỗi hình ảnh trong dự án bây giờ có một thuộc tính tĩnh được xác định.
Colors: Tất cả các màu của ứng dụng cũng có các thuộc tính tĩnh để tham khảo.
Mở Assets.xcassets . Lưu ý rằng có một nhóm hình ảnh có tên là nội dung khởi chạy . Nhưng Assets đã khai báo tất cả các thuộc tính ảnh tĩnh ở cùng một mức. SwiftGen có thể duy trì tổ chức này cho bạn nhưng không làm như vậy theo mặc định. Mở swiftgen.yml và thay thế toàn bộ mục nhập xcassets bằng mục sau:
Tại đây, bạn tận dụng khả năng của SwiftGen để tùy chỉnh đầu ra mã của bạn bằng cách thực hiện như sau:
Xác định params trên outputs của bạn.
forceProvidesNamespaces: Điều này sẽ duy trì không gian tên của bạn được tìm thấy trong danh mục nội dung.
Tham số bổ sung này đảm bảo cho dù bạn đã cung cấp bao nhiêu tên tệp inputs, SwiftGen sẽ duy trì các bảng liệt kê riêng biệt để đại diện cho từng danh mục nội dung.
Xây dựng dự án, sau đó quay lại XCAssets + Generated.swift . Bây giờ bạn sẽ thấy Assets có một enum tên mới LaunchAssets để đại diện cho cấu trúc thư mục của bạn.
Bây giờ, đã đến lúc sử dụng mã mới được tạo này để xóa bất kỳ tham chiếu chuỗi nào đến hình ảnh. Mở DrinksListView.swift . Bạn sẽ thấy Image("milkshake")bên trong các mục trên thanh công cụ được thêm vào chế độ xem. Thay thế dòng bằng dòng sau:
Image(Asset.Assets.milkshake.name)
Ở đây, bạn đã tham chiếu tên hình ảnh cho milkshake. Hiện tại, SwiftGen không hỗ trợ làm việc trực tiếp với SwiftUI. Bạn sẽ học cách tự thêm cái này sau. Hiện tại, bạn vẫn có thể sử dụng những gì có sẵn để tải nội dung hình ảnh mà không cần tham chiếu trực tiếp đến chuỗi.
Sử dụng các mẫu cơ bản bổ sung
Có một số mẫu bổ sung mà bạn có thể tận dụng mà không cần tùy chỉnh.
Làm việc với trình tạo giao diện
Ứng dụng bạn đang làm việc đang sử dụng SwiftUI. Tuy nhiên, để giới thiệu khả năng của SwiftGen để làm việc với Trình tạo giao diện, dự án mẫu bao gồm một bảng phân cảnh và một số bộ điều khiển chế độ xem. Bắt đầu bằng cách tạo mã để hỗ trợ Trình tạo giao diện hoặc Bảng phân cảnh bằng cách thêm phần sau vào swiftgen.yml :
Cho SwiftGen biết rằng bạn muốn nó tìm kiếm bất kỳ tệp nào được hỗ trợ trình tạo giao diện trong thư mục gốc của dự án của bạn.
Một điều tuyệt vời về SwiftGen là nó tách biệt khái niệm Cảnh khỏi Segues . Điều này cho biết tệp mà cảnh của bạn sẽ xuất ra.
Cuối cùng, điều này cho biết tất cả thông tin giả mạo sẽ được xuất ra ở đâu.
Xây dựng dự án của bạn. Thêm IB-Scenes + Generate.swift và IB-Segues + Generated.swift vào nhóm Generated , giống như bạn đã làm đối với XCAssets + Generated.swift .
Giờ đây, bạn có thể thay thế thông tin cảnh hoặc thông tin xác thực trong ứng dụng.
Bắt đầu bằng cách mở InformationViewController.swift . Trong InformationViewController, thay thế việc triển khai showAbout()bằng những điều sau:
Tất cả các segues sẽ tạo ra dưới dạng các enum trường hợp thực tế, điều này giúp mọi thứ dễ dàng hơn khi bạn kiểm tra xem segue nào được kích hoạt. Vì đơn giản, mã bạn đã thêm chỉ đang kích hoạt segue.
Tiếp theo, bên trong InformationView, thay thế makeUIViewController(context:)bằng những thứ sau:
func makeUIViewController(context: Context) -> some UIViewController {
StoryboardScene.Main.initialScene.instantiate()
}
Ở đây, bạn đã đơn giản hóa mã cần thiết để khởi tạo bộ điều khiển chế độ xem ban đầu của bảng phân cảnh. SwiftGen cung cấp một phương thức trợ giúp để nhanh chóng truy cập cảnh ban đầu của bạn.
Xây dựng và chạy. Nhấn vào Tìm hiểu thêm ở trên cùng bên phải. Bạn sẽ thấy một phương thức xuất hiện, hiển thị cho bạn một số thông tin về ứng dụng. Điều này được gọi vào makeUIViewController(context:)để biết chế độ xem nào cần tải.
Bây giờ, hãy nhấn vào nút About . Bạn sẽ kích hoạt segue mà bạn đã sửa đổi.
Làm việc với JSON
Cuối cùng chúng ta sẽ thêm phần hỗ trợ cho JSON. Thêm phần sau vào cuối swiftgen.yml :
Điều này hiện cung cấp một cách để tham chiếu đến bất kỳ tệp JSON nào bạn có trong tài nguyên của dự án. Bạn sẽ sử dụng điều này để chuyển đổi dữ liệu giả đi kèm với ứng dụng.
Xây dựng dự án, sau đó thêm JSON + Generated.swift vào nhóm Đã tạo của bạn .
Bây giờ, hãy mở Drink.swift . Loại bỏ hoàn toàn các struct tên được đặt tên MockData. Sau đó, thay thế phần mở rộng cho DrinkStore, được tìm thấy ở dưới cùng, bằng phần mở rộng sau:
extension DrinkStore {
static var mockData: [Drink] {
do {
let data = try JSONSerialization.data(
withJSONObject: JSONFiles.starterDrinks,
options: [])
let mockDrinks = try JSONDecoder().decode([Drink].self, from: data)
return mockDrinks
} catch {
print(error.localizedDescription)
return []
}
}
}
Tại đây, bạn sẽ thấy các tham chiếu mã của mình JSONFiles.starterDrinks. Mở MockData.json và nhận thấy khóa đầu tiên trong tệp, có tên là starterDrinks . SwiftGen đã lấy đối tượng cấp cao nhất này và cung cấp nó dưới dạng thuộc tính tĩnh trên JSONFiles để bạn tham khảo khi cần.
Xây dựng và chạy. Bạn sẽ không nhận thấy bất cứ điều gì khác so với trước đây – chỉ là đồ uống hiển thị trong danh sách.
Làm việc với chuỗi
Có lẽ một trong những tiện ích lớn nhất mà SwiftGen cung cấp là khả năng sử dụng các biến để tham chiếu các chuỗi được bản địa hóa(Localized Strings). Đó là một thực tiễn tuyệt vời để đặt bất kỳ văn bản nào bạn sẽ trình bày cho người dùng ứng dụng của mình bên trong các tệp strings hoặc tệp stringsdict được bản địa hóa . Nhưng nếu bạn đã làm điều này, bạn biết rằng một khi bạn vượt quá một số chuỗi, sẽ trở nên khó khăn để nhớ những chuỗi nào có sẵn. Cũng có cảm giác thừa rằng bạn có một chuỗi trong tệp chuỗi và… một chuỗi trong mã của bạn.
Dự án này chứa các tệp chuỗi sau:
Localizable.strings : Tệp chuỗi run of the-mill của bạn, được tạo bằng các khóa và giá trị.
Localizable.stringsdict : Bạn nên sử dụng các tệp stringdict bất cứ khi nào bạn cần lo lắng về các chuỗi đa nguyên. Loại tệp này không chỉ hỗ trợ dịch các chuỗi mà còn hỗ trợ cách đa hóa các từ cho bất kỳ biến thể nào mà một ngôn ngữ yêu cầu.
Để chuyển đổi tất cả các tệp chuỗi của bạn, hãy thêm phần sau vào swiftgen.yml :
Bạn nên biết một số điều quan trọng về những gì bạn đã thêm ở đây:
Khi bạn chuyển đổi các tệp chuỗi của mình, bạn chỉ nên sử dụng một trong các thư mục được bản địa hóa. Đối với mỗi ngôn ngữ được thêm vào, một thư mục bản địa hóa mới sẽ được tạo. Trong dự án này, bản địa hóa duy nhất là tiếng Anh. Nếu bạn thêm nhiều ngôn ngữ hơn, không cần phải sửa đổi mục nhập này để nhận các bản dịch bổ sung đó. Bởi vì tệp chuỗi phải có một tập hợp các khóa và giá trị phù hợp, bạn sẽ tham chiếu các bản dịch giống như bạn sẽ làm nếu bạn không sử dụng SwiftGen.
Bạn đã thêm một tham số mới, được đặt tên publicAccess. Nếu bạn nhìn xung quanh bất kỳ tệp nào đã tạo mà bạn đã thêm, bạn sẽ thấy tất cả các loại đều có công cụ sửa đổi quyền truy cập internal. Bằng cách sử dụng tham số này, bạn có thể thay đổi công cụ sửa đổi quyền truy cập của các bảng kê đã tạo thành công khai.
Build dự án, sau đó thêm Strings + Generated.swift vào nhóm Generated . Trước khi bạn chuyển đổi tất cả các chuỗi trong ứng dụng, điều quan trọng là phải hiểu tệp này hơi khác một chút như thế nào.
Hiểu tệp
Mở Strings + Generated.swift . Bạn sẽ thấy kiểu cha được tạo, được đặt tên L10n. Trong phần này enum, bạn sẽ thấy một số kiểu con được tạo bên trong nó DrinkDetail: Navigationvà DrinkList. Chúng tương ứng với các chuỗi được khai báo trong Localizable.strings .
Mở Localizable.strings và xem cách nó khai báo mục nhập đầu tiên:
"DrinkList.Navigation.Title" = "Drinks";
Lưu ý cách khai báo khóa bằng ký hiệu không gian tên bằng dấu chấm:
DrinkList : Điều này cho biết chuỗi này thuộc về màn hình Danh sách đồ uống.
Navigation : Cho biết chuỗi này sẽ được sử dụng trong thanh điều hướng.
Title : Cuối cùng, điều này cho biết đó là tiêu đề trong thanh điều hướng.
Bây giờ, bạn có thể chọn tổ chức và đặt tên cho các chuỗi của mình theo cách khác. Không có gì sai với điều đó – kiểu đặt tên này được sử dụng để hiển thị cách SwiftGen sẽ chuyển đổi và tổ chức mã của bạn. Đối với mỗi khoảng thời gian bạn đặt trong chuỗi của mình, SwiftGen sẽ tạo thêm một kiểu con.
Quay lại Strings + Generated.swift , bạn sẽ thấy hàm tĩnh drinksCount. SwiftGen giúp bạn dễ dàng làm việc với các chuỗi đa nguyên. Thay vì phải tạo tham chiếu đến các chuỗi được bản địa hóa và sử dụng trình định dạng chuỗi, các hàm được tạo này giúp bạn dễ dàng sử dụng một hàm lấy các giá trị của chuỗi đa nguyên của bạn.
Bây giờ, chuyển đổi tất cả các chuỗi bản địa hóa được sử dụng trong ứng dụng để trỏ đến các loại được tạo. Bắt đầu bằng cách mở DrinksListView.swift . Tiếp theo, tìm dòng mã:
Text("DrinkList.Navigation.Title")
Đổi nó thành
Text(L10n.DrinkList.Navigation.title)
Chờ một chút… L10n là gì? Đây là một phím tắt cho “bản địa hóa”. Bạn cũng có thể thấy “quốc tế hóa” được viết tắt là i18n . Nếu bạn đếm các chữ cái giữa chữ cái đầu tiên và chữ “n” cuối cùng trong một trong hai từ, bạn sẽ tìm thấy 10 hoặc 18 chữ cái tương ứng. Mặc dù điều này có ý nghĩa, sẽ không tốt nếu sử dụng một tên khác cho loại chuỗi cấp cao nhất của bạn phải không?
Mở swiftgen.yml và thêm một thuộc tính vào mục nhập chuỗi của bạn, ngay sau publicAccessđó, nó trông giống như sau:
Ở đây, bạn đã thêm tham số enumName. Điều này cho phép bạn thay đổi loại từ “L10n” thành “Strings”.
Xây dựng và chạy. Lần này, bạn sẽ có một lỗi biên dịch. Điều này là do loại L10n không còn nữa. Truy cập DrinksListView.swift và tìm:
Text(L10n.DrinkList.Navigation.title)
Thay thế nó bằng
Text(Strings.DrinkList.Navigation.title)
Bây giờ, ứng dụng của bạn đang sử dụng tên loại mới mà bạn đã cung cấp ở bước trước.
Build ứng dụng của bạn. Bạn sẽ không còn gặp bất kỳ lỗi biên dịch nào nữa.
Lưu ý : Nếu bạn vẫn gặp lỗi, bạn có thể cần phải làm sạch dự án của mình. Chọn Product ▸ Clean and build folders , sau đó tạo lại.
Tiếp theo tìm tài sản drinkCountString. Đây là mã sử dụng tệp stringdict để xử lý cách hiển thị số lượng đồ uống trong danh sách. Thay thế nó bằng những thứ sau:
private var drinkCountString: String {
Strings .drinksCount (drinkStore.drinks.count)
}
Nếu bạn so sánh nó với mã ở đó trước đây, bạn có thể thấy đây là cách nhanh hơn nhiều để tham chiếu các chuỗi đa nguyên.
Bạn nên chuyển đổi tất cả các chuỗi trong dự án khỏi sử dụng chuỗi. Mở Localizable.strings và xem tất cả các khóa chuỗi. Bạn nên tìm từng cách sử dụng các khóa này trong một tệp Swift và hoán đổi nó cho các biến được tạo bởi SwiftGen.
Khi bạn hoán đổi văn bản xếp hạng trong DrinkDetailView.swift , nó sẽ sử dụng một hàm để cung cấp chuỗi, giống như cách bạn xử lý số lượng đồ uống.
Tạo mẫu tùy chỉnh
Cho đến thời điểm này, tệp swiftgen.yml của bạn đã sử dụng các mẫu mặc định do SwiftGen cung cấp. Tất cả những thứ này được lưu trữ trong SwiftGen pod. Nếu các mẫu này không cung cấp đầy đủ chức năng bạn muốn, bạn có thể tạo các mẫu của riêng mình để tạo mã theo cách bạn muốn. Các mẫu được xây dựng bằng Stencil , một dự án mã nguồn mở cung cấp ngôn ngữ tạo mẫu cho Swift. Trong phần này, bạn sẽ học cách sửa đổi các mẫu hiện có và sử dụng chúng để tạo mã của bạn.
Nếu bạn nhìn trong trình điều hướng Dự án, bạn sẽ thấy có một thư mục có tên là Mẫu . Trong đó, có hai thư mục con: Fonts và xcassets . Với những điều này, bạn sẽ được SwiftGen cung cấp hỗ trợ để sử dụng màu sắc và phông chữ trực tiếp trong SwiftUI.
Hỗ trợ màu SwiftUI
Để thêm Colorhỗ trợ SwiftUI, hãy mở asset_swift5_swiftui.stencil . Lúc đầu, mọi thứ có thể hơi choáng ngợp vì tệp không có bất kỳ hỗ trợ cú pháp mã nào.
Trên dòng 13, thêm dòng mã sau:
import SwiftUI
Tiếp theo, quay lại swiftgen.yml . Trong mục nhập đầu tiên của bạn, đối với xcassets , hãy tìm dòng nơi bạn xác định tên mẫu:
Tại đây, bạn đã thay đổi từ sử dụng templateNamesang templatePath. Điều này yêu cầu SwiftGen sử dụng mẫu tùy chỉnh của bạn thay vì mẫu tích hợp sẵn.
Xây dựng dự án, sau đó truy cập XCAssets + Generated.swift . Ở trên cùng, bây giờ bạn sẽ thấy:
import SwiftUI
Vì bạn đã thêm nhập vào tệp Stencil nên khi mã được tạo, mã sẽ chọn thay đổi này và thêm nhập mới. Khá tuyệt, phải không?
Mở asset_swift5_swiftui.stencil và thay thế:
// Add Support For SwiftUI Here
Như dưới đây:
{{accessModifier}} private(set) lazy var color: Color = {
Color(systemColor)
}()
Trong đoạn mã này, bạn có thể xem cách sử dụng Stencil nhiều hơn một chút. Đây là những gì bạn đã thêm:
{{accessModifier}}: Đây là cách bạn nói với Stencil cách thay thế bằng thứ gì đó được cung cấp trong quá trình tạo mã. Nếu bạn nhìn vào dòng 11 của tệp này, bạn sẽ thấy accessModifier được định nghĩa là một biến. Theo mặc định, công cụ sửa đổi quyền truy cập là internal. Nếu bạn nhớ từ trước, bạn đã thấy cách bạn có thể thay đổi điều này thànhpublic
Phần còn lại của điều này thực sự chỉ là mã tiêu chuẩn. Nó tạo ra màu SwiftUI từ màu UIKit.
Lưu ý : Có một sửa đổi khác được thực hiện đối với mẫu như một phần của vật liệu khởi động. Nó thay đổi loại Colorthành SystemColor.
Build lại dự án, sau đó quay lại XCAssets + Generated.swift . Trên dòng 62, bạn sẽ thấy mã từ mẫu của mình, hiện được tạo dưới dạng mã thực.
Bây giờ, bạn cần hoán đổi bất kỳ tham chiếu được mã hóa cứng nào thành một màu để sử dụng chức năng mới của mình. Mở DrinksListView.swift và tìm vị trí đặt màu nền trước trên văn bản tiêu đề điều hướng. Thay thế nó như sau:
Đây systemColor là tham chiếu đến loại màu cụ thể của nền tảng. Sẽ như UIColor vậy nếu bạn đang sử dụng iOS và NSColor nếu bạn đang sử dụng macOS.
Hai tệp có màu được khai báo bằng cách sử dụng chuỗi:
AppMain.swift
DrinkListView.swift
Hoàn tất chuyển đổi các màu còn lại từ việc sử dụng các chuỗi trong mỗi tệp này theo ví dụ trên.
Hỗ trợ Phông chữ SwiftUI
Cũng như SwiftUI Color hiện không được hỗ trợ trong SwiftGen, Font cũng không được hỗ trợ. Bắt đầu bằng cách mở font_swift5_swiftui.stencil và thêm nhập sau vào đầu tệp:
import SwiftUI
Tiếp theo, thay thế khối mã này được tìm thấy gần cuối tệp:
// Add Support For SwiftUI here
fileprivate extension Font {
}
Bằng đoạn code dưới đây:
fileprivate extension Font {
// 1
static func mappedFont(_ name: String, textStyle: TextStyle) -> Font {
let fontStyle = mapToUIFontTextStyle(textStyle)
let fontSize = UIFont.preferredFont(forTextStyle: fontStyle).pointSize
return Font.custom(name, size: fontSize, relativeTo: textStyle)
}
// 2
static func mapToUIFontTextStyle(
_ textStyle: SwiftUI.Font.TextStyle
) -> UIFont.TextStyle {
switch textStyle {
case .largeTitle:
return .largeTitle
case .title:
return .title1
case .title2:
return .title2
case .title3:
return .title3
case .headline:
return .headline
case .subheadline:
return .subheadline
case .callout:
return .callout
case .body:
return .body
case .caption:
return .caption1
case .caption2:
return .caption2
case .footnote:
return .footnote
@unknown default:
fatalError("Missing a TextStyle mapping")
}
}
}
Đây là những gì bạn đã thêm:
mappedFont(_:textStyle:)tạo một phông chữ tùy chỉnh từ tên và TextStyle. Kiểu được sử dụng để lấy kích thước phông chữ tiêu chuẩn, mặc định cho mỗi phông chữ.
mapToUIFontTextStyle(_:)chỉ đơn giản là cung cấp ánh xạ 1: 1 của SwiftUI TextStyletới UIKitTextStyle
Khối mã này tương tự như những gì bạn đã thêm để cung cấp Color hỗ trợ. Sự khác biệt duy nhất ở đây là nó dành riêng cho việc cung cấp phông chữ trực tiếp trong SwiftUI.
Ứng dụng này sử dụng hai phông chữ, cả hai đều nằm trong nhóm Tài nguyên :
NotoSans
NotoSans-Bold
Mục nhập mới này chỉ cần biết thư mục mẹ của phông chữ bạn muốn hỗ trợ ở đâu. Mọi thứ khác tương tự như tất cả các mục nhập khác mà bạn đã thêm trước đó.
Xây dựng ứng dụng của bạn và thêm Fonts+Generated.swift vào Generated . Sau khi thực hiện, hãy mở Fonts+Generated.swift . Tại đây, bạn có thể thấy cách tổ chức họ phông chữ. Bạn sẽ thấy rằng NotoSans có sẵn các biến thể sau:
Thường
In đậm
In đậm nghiêng
In nghiêng
Giống như tất cả các mã được tạo khác trong ứng dụng, nó khá dễ sử dụng. Mở AppMain.swift và thay thế dòng đầu tiên application(_:didFinishLaunchingWithOptions:)bằng dòng sau:
let buttonFont = FontFamily.NotoSans.bold.font(size: 16)
Tại đây, bạn đặt trực tiếp kích thước phông chữ thành phông chữ NotoSans Bold(in đậm) .
Tiếp theo, truy cập DrinksListView.swift , tìm tham chiếu đầu tiên đến một phông chữ, trong NavigationLink và thay thế nó bằng như sau:
Tại đây, bạn tận dụng mã của mẫu tùy chỉnh của mình để có thể tạo phông chữ SwiftUI tùy chỉnh, kích thước phù hợp với mặc định TextStyle: trong trường hợp này body.
Cuối cùng, hoàn tất việc chuyển đổi tất cả các cách sử dụng của phông chữ trong toàn bộ ứng dụng. Trong cả DrinksListView.swift và DrinkDetailView.swift , bạn sẽ tìm thấy một số nơi đặt phông chữ. Theo ví dụ trên, bạn có thể chuyển đổi mã từ việc sử dụng một chuỗi sang trọng số thích hợp của NotoSans . Mỗi vị trí trong số này đã cho biết TextStylechúng nên có vị trí nào.
Build và Run. Ứng dụng của bạn trông vẫn giống như cách nó hoạt động khi bạn bắt đầu. Nhưng bây giờ bạn sẽ có tất cả các tài nguyên được tham chiếu theo cách an toàn về kiểu loại!
Tổng kết
Bây giờ bạn có thể sử dụng SwiftGen để:
Loại bỏ nhu cầu sử dụng chuỗi để tham chiếu tài nguyên trong ứng dụng của bạn, cho dù bạn sử dụng SwiftUI hay UIKit.
Tùy chỉnh các tệp đầu ra bằng cách sử dụng các thông số cài sẵn.
Sử dụng các mẫu của riêng bạn để tạo mã.
Để tìm hiểu thêm về nó, hãy xem SwiftGen trên GitHub . Bạn cũng có thể tìm hiểu thêm về Stencil trên GitHub .
Chúng tôi hy vọng bạn thích hướng dẫn này. Nếu bạn có bất kỳ câu hỏi hoặc ý kiến nào, hãy tham gia thảo luận của diễn đàn bên dưới!
Nội dung bài viết được dịch từ link: https://www.raywenderlich.com/23709326-swiftgen-tutorial-for-ios
Trong thời đại công nghệ 4.0, các công ty đua nhau chuyển đổi số, vì vậy có rất nhiều những ứng dụng di động được phát triển để giúp tiếp cận người dùng một cách dễ dàng hơn. Để những ứng dụng có thể vươn xa ra tầm thế giới, tiếp cận được với những người dùng nước ngoài, thì ứng dụng đó cần phải hỗ trợ đa ngôn ngữ. Vì vậy hôm nay mình sẽ hướng dẫn các bạn một số cách thực hiện một ứng dụng iOS hỗ trợ đa ngôn ngữ.
Cài đặt dự án hỗ trợ đa ngôn ngữ
Bước 1: Thực hiện thêm ngôn ngữ hỗ trợ bằng cách Chọn Project -> Info(thông tin) -> Bấm nút +
Thêm ngôn ngữBỏ tích ở các file storyboard để xCode không gen ra các file String cho các files storyboard
Bước 2: Tạo file String để chứa nội dung theo các ngôn ngữ New File… -> tìm string và chọn Strings File -> Next
Bước 3: Localize file string vừa mới tạo Chọn File vừa tạo -> Bấm vào nút Localize… -> Popup hiển thị lên thì chọn Localize
Bước 4: Tích vào ngôn ngữ bạn hỗ trợ, Chọn File String localize vừa được tạo -> Ở menu bên phải mục Localization tích vào những ngôn ngữ mà ứng dụng của bạn hỗ trợ.
Chọn ngôn ngữ hỗ trợ
Vậy là việc cài đặt đa ngôn ngữ cho ứng dụng của bạn đã hoàn thành.
Thực hiện đa ngôn ngữ với các chuỗi (Localize String)
Để tránh các lỗi sai chính tả và việc thực hiện đa ngôn ngữ trở nên dễ dàng hơn thì chúng ta sẽ tạo ra một enum Localization để liệt kê các item/string dưới dạng keyword để sử dụng như sau:
enum Localization {
static let helloWorld: String = "Hello"
static let buttonChangeLanguageTitle: String = "btn.changeLanguage"
}
Với mỗi 1 key của string chúng ta cần tạo ra value tương ứng với nó ở trong file Localizable mà chúng ta đã tạo.
Thêm key/value cho các file ngôn ngữ tương ứng:
Thêm key/value cho ngôn ngữ Tiếng AnhThêm key/value cho ngôn ngữ Tiếng Việt
NOTE: – Kết thúc của dòng code phải là dấu chấm phẩy “;” nếu 1 dòng code bị thiếu nó sẽ khiến ứng dụng của bạn hiển thị không đúng ngôn ngữ. – Key phải trùng với giá trị của enum Localization
2. Tạo một file chung để quản lí ngôn ngữ như đoạn code dưới đây
// for manage language
class LanguageManager {
static let shared = LanguageManager()
private init(){}
// save language to UserDefault
func changeLanguage(_ language: Language) {
UserDefaults.standard.set(language.rawValue, forKey: "APP_LANGUAGE")
}
/// Get language of set on app, if nil use device language
/// - Returns: current language of application
func getAppLanguage() -> Language {
if let language = UserDefaults.standard.value(forKey: "APP_LANGUAGE") as? String {
return Language(rawValue: language) ?? .english
} else {
// default lan is english
let currentLanguage: String = Locale.current.languageCode ?? Language.english.rawValue
return Language(rawValue: currentLanguage) ?? .english
}
}
// define enum language
enum Language: String {
case vietnamese = "vi"
case english = "en"
}
}
Ở đây mình tạo ra một file quản lí ngôn ngữ nhằm mục đích tập trung tất cả những tính năng liên quan tới ngôn ngữ: thay đổi, lấy ra ngôn ngữ …
Chúng ta cần lưu ngôn ngữ của ứng dụng vào Local Storage để khi người dùng tắt ứng dụng vào lại thay đổi vẫn được áp dụng. Trong trường hợp này mình chọn cách dùng UserDefault vì những lí do sau: – Dễ sử dụng – Dữ liệu cần lưu không yêu cầu bảo mật – Dữ liệu lưu có dung lượng nhỏ
3. Vậy là chúng ta đã tạo xong file quản lí ngôn ngữ. Tiếp đến để việc sử dụng localization dễ dàng chúng ta sẽ tạo ra một var localized trong extension String như sau:
Sau khi tạo xong extension thì việc thực hiện code localized trở nên rất dễ dàng. Khi cần sử dụng chúng ta chỉ cần .localized là xong.
4. Demo và cách sử dụng:
Trong ví dụ này mình sẽ tạo ra 1 label hiển thị text và một nút để thay đổi ngôn ngữ của ứng dụng. Sau đó mình tạo ra một func setDataForUI() nhằm mục đích set tất cả các data liên quan tới localization ở đây. Nếu sau đó đổi ngôn ngữ ta chỉ cần gọi lại hàm này để thực hiện set lại.
Vậy là chúng ta đã hoàn thành việc thực hiện localize String cho ứng dụng. Mình hi vọng bài viết này có thể giúp được chút gì đó cho các bạn. Cảm ơn các bạn đã đọc bài viết này, chúc các bạn thành công!