Tìm hiểu thực tiễn tốt nhất về iOS bằng cách xây dựng một ứng dụng công thức nấu ăn đơn giản

Nguồn: ChefStep

Mục lục

  • Bắt đầu
  • Phiên bản Xcode và Swift
  • Phiên bản iOS tối thiểu để hỗ trợ
  • Tổ chức dự án Xcode
  • Bí quyết cấu trúc ứng dụng
  • Quy ước mã
  • Tài liệu
  • Đánh dấu các phần của mã
  • Kiểm soát nguồn
  • Phụ thuộc
  • Bắt tay vào dự án
  • API
  • Màn hình khởi chạy
  • Biểu tượng ứng dụng
  • Mã lint với SwiftLint
  • Loại tài nguyên an toàn
  • Cho tôi xem mã
  • Thiết kế mô hình
  • Điều hướng tốt hơn với FlowControll
  • Giao diện tự động
  • Ngành kiến ​​trúc
  • Bộ điều khiển xem lớn
  • Kiểm soát truy cập
  • Tính chất lười biếng
  • Đoạn mã
  • Mạng
  • Cách kiểm tra mã mạng
  • Triển khai bộ đệm để hỗ trợ ngoại tuyến
  • Cách kiểm tra Cache
  • Đang tải hình ảnh từ xa
  • Giúp tải hình ảnh thuận tiện hơn cho UIImageView
  • Nguồn dữ liệu chung cho UITableView và UICollectionView
  • Bộ điều khiển và khung nhìn
  • Xử lý trách nhiệm với một đứa trẻ Xem điều khiển
  • Thành phần và tiêm phụ thuộc
  • Ứng dụng bảo mật vận tải
  • Chế độ xem có thể cuộn tùy chỉnh
  • Thêm chức năng tìm kiếm
  • Hiểu ngữ cảnh trình bày
  • Hành động tìm kiếm
  • Thử nghiệm thảo luận với kỳ vọng đảo ngược
  • Kiểm tra giao diện người dùng với UITests
  • Bảo vệ chủ đề chính
  • Đo lường hiệu suất và các vấn đề
  • Tạo mẫu với Sân chơi
  • Đi đâu từ đây

Tôi bắt đầu phát triển iOS khi iOS 7 đã được công bố. Và tôi đã học được một chút, thông qua làm việc, lời khuyên từ các đồng nghiệp và cộng đồng iOS.

Trong bài viết này, tôi đã chia sẻ rất nhiều cách thực hành tốt bằng cách lấy ví dụ về một ứng dụng công thức nấu ăn đơn giản. Mã nguồn có tại GitHub Recipes.

Ứng dụng này là một ứng dụng chi tiết tổng thể truyền thống hiển thị một danh sách các công thức nấu ăn cùng với thông tin chi tiết của họ.

Có hàng ngàn cách để giải quyết vấn đề, và cách giải quyết vấn đề cũng phụ thuộc vào sở thích cá nhân. Hy vọng rằng, trong suốt bài viết này, bạn sẽ học được điều gì đó hữu ích - tôi đã học được rất nhiều khi thực hiện dự án này.

Tôi đã thêm các liên kết đến một số từ khóa mà tôi cảm thấy việc đọc thêm sẽ có ích. Vì vậy, chắc chắn kiểm tra chúng ra. Bất kỳ thông tin phản hồi đều được chào đón.

Vậy hãy bắt đầu…

Dưới đây là tổng quan cấp cao về những gì bạn sẽ xây dựng.

Bắt đầu

Hãy để quyết định về các công cụ và cài đặt dự án mà chúng tôi sử dụng.

Phiên bản Xcode và Swift

Tại WWDC 2018, Apple đã giới thiệu Xcode 10 với Swift 4.2. Tuy nhiên, tại thời điểm viết bài, Xcode 10 vẫn đang trong giai đoạn thử nghiệm 5. Vì vậy, hãy để Lôi gắn bó với Xcode 9 và Swift 4.1 ổn định. Xcode 4.2 có một số tính năng thú vị - bạn có thể chơi với nó thông qua Sân chơi tuyệt vời này. Nó không giới thiệu những thay đổi lớn từ Swift 4.1, vì vậy chúng tôi có thể dễ dàng cập nhật ứng dụng của mình trong tương lai gần nếu được yêu cầu.

Bạn nên đặt phiên bản Swift trong cài đặt Dự án thay vì cài đặt đích. Điều này có nghĩa là tất cả các mục tiêu trong dự án đều có chung phiên bản Swift (4.1).

Phiên bản iOS tối thiểu để hỗ trợ

Kể từ mùa hè 2018, iOS 12 đang ở phiên bản beta 5 công khai và chúng tôi không thể nhắm mục tiêu iOS 12 mà không có Xcode 10. Trong bài đăng này, chúng tôi sử dụng Xcode 9 và SDK cơ sở là iOS 11. Tùy thuộc vào yêu cầu và cơ sở người dùng, một số ứng dụng cần hỗ trợ các phiên bản iOS cũ. Mặc dù người dùng iOS có xu hướng chấp nhận các phiên bản iOS mới nhanh hơn so với những người sử dụng Android, nhưng có một số người vẫn ở lại với các phiên bản cũ. Theo lời khuyên của Táo, chúng tôi cần hỗ trợ hai phiên bản mới nhất là iOS 10 và iOS 11. Theo đo lường của App Store vào ngày 31 tháng 5 năm 2018, chỉ có 5% người dùng sử dụng iOS 9 trở về trước.

Nhắm mục tiêu các phiên bản iOS mới có nghĩa là chúng tôi có thể tận dụng các SDK mới, được các kỹ sư của Apple cải thiện hàng năm. Trang web của nhà phát triển Apple có chế độ xem nhật ký thay đổi được cải thiện. Bây giờ dễ dàng hơn để xem những gì đã được thêm hoặc sửa đổi.

Lý tưởng nhất là để xác định thời điểm bỏ hỗ trợ cho các phiên bản iOS cũ, chúng tôi cần phân tích về cách người dùng sử dụng ứng dụng của chúng tôi.

Tổ chức dự án Xcode

Khi chúng tôi tạo dự án mới, hãy chọn cả hai Thử nghiệm đơn vị Bao gồm đơn vị và các thử nghiệm Bao gồm các thử nghiệm UI vì đây là một cách thực hành được khuyến nghị để viết thử nghiệm sớm. Những thay đổi gần đây đối với khung XCTest, đặc biệt là trong Kiểm tra giao diện người dùng, giúp việc kiểm tra trở nên dễ dàng và khá ổn định.

Trước khi thêm các tệp mới vào dự án, hãy tạm dừng và suy nghĩ về cấu trúc của ứng dụng của bạn. Làm thế nào để chúng tôi muốn tổ chức các tập tin? Chúng tôi có một vài lựa chọn. Chúng tôi có thể tổ chức các tệp theo tính năng / mô-đun hoặc vai trò / loại. Mỗi cái đều có ưu và nhược điểm và tôi sẽ thảo luận về chúng dưới đây.

Theo vai trò / loại:

  • Ưu điểm: Có ít suy nghĩ liên quan đến nơi để đặt các tập tin. Nó cũng dễ dàng hơn để áp dụng các tập lệnh hoặc bộ lọc.
  • Nhược điểm: Nó khó tương quan nếu chúng ta muốn tìm nhiều tệp liên quan đến cùng một tính năng. Sẽ mất thời gian để sắp xếp lại các tệp nếu chúng ta muốn biến chúng thành các thành phần có thể tái sử dụng trong tương lai.

Theo tính năng / mô-đun

  • Ưu điểm: Nó làm cho mọi thứ mô-đun và khuyến khích thành phần.
  • Nhược điểm: Nó có thể trở nên lộn xộn khi nhiều tệp thuộc các loại khác nhau được gói lại với nhau.

Duy trì mô-đun

Cá nhân, tôi cố gắng tổ chức mã của mình theo các tính năng / thành phần càng nhiều càng tốt. Điều này giúp dễ dàng xác định mã liên quan để sửa lỗi và thêm các tính năng mới dễ dàng hơn trong tương lai. Nó trả lời câu hỏi Ứng dụng này làm gì? Thay vì tập tin Đây là gì? Đây là một bài viết hay về vấn đề này.

Một nguyên tắc tốt là duy trì sự nhất quán, bất kể bạn chọn cấu trúc nào.

Bí quyết cấu trúc ứng dụng

Sau đây là cấu trúc ứng dụng mà ứng dụng công thức của chúng tôi sử dụng:

Nguồn

Chứa các tệp mã nguồn, chia thành các thành phần:

  • Các tính năng: các tính năng chính trong ứng dụng
  • Trang chủ: màn hình chính, hiển thị danh sách các công thức nấu ăn và tìm kiếm mở
  • Danh sách: hiển thị danh sách các công thức nấu ăn, bao gồm tải lại công thức và hiển thị chế độ xem trống khi công thức không tồn tại
  • Tìm kiếm: xử lý tìm kiếm và gỡ lỗi
  • Chi tiết: hiển thị thông tin chi tiết

Thư viện

Chứa các thành phần cốt lõi của ứng dụng của chúng tôi:

  • Flow: chứa FlowContoder để quản lý các luồng
  • Bộ điều hợp: nguồn dữ liệu chung cho UICollectionView
  • Tiện ích mở rộng: tiện ích mở rộng thuận tiện cho các hoạt động chung
  • Model: Mô hình trong ứng dụng, được phân tích cú pháp từ JSON

Nguồn

Chứa các tập tin plist, tài nguyên và Storyboard.

Quy ước mã

Tôi đồng ý với hầu hết các hướng dẫn về phong cách trong raywenderlich / swift-style-guide và github / swift-style-guide. Đây là những cách đơn giản và hợp lý để sử dụng trong một dự án Swift. Ngoài ra, hãy xem Nguyên tắc thiết kế API chính thức do nhóm Swift tại Apple thực hiện để biết cách viết mã Swift tốt hơn.

Bất kỳ hướng dẫn phong cách nào bạn chọn để làm theo, mã rõ ràng phải là mục tiêu quan trọng nhất của bạn.

Sự thụt lề và cuộc chiến không gian tab là một chủ đề nhạy cảm, nhưng một lần nữa, nó phụ thuộc vào hương vị. Tôi sử dụng bốn khoảng cách thụt lề trong các dự án Android và hai khoảng trắng trong iOS và React. Trong ứng dụng Bí quyết này, tôi theo dõi thụt đầu dòng nhất quán và dễ lý do, mà tôi đã viết về đây và đây.

Tài liệu

Mã tốt nên giải thích rõ ràng để bạn không cần viết bình luận. Nếu một đoạn mã khó hiểu, thì tốt nhất là tạm dừng và cấu trúc lại nó theo một số phương thức có tên mô tả để nó mã hóa đoạn mã rõ ràng hơn để hiểu. Tuy nhiên, tôi thấy các lớp tài liệu và phương pháp cũng tốt cho đồng nghiệp và bản thân tương lai của bạn. Theo hướng dẫn thiết kế API Swift,

Viết bình luận tài liệu cho mỗi tuyên bố. Những hiểu biết thu được bằng cách viết tài liệu có thể có tác động sâu sắc đến thiết kế của bạn, vì vậy don don đã bỏ nó đi.

Nó rất dễ dàng để tạo mẫu nhận xét /// trong Xcode với Cmd + Alt + /. Nếu bạn dự định cấu trúc lại mã của mình thành một khung để chia sẻ với những người khác trong tương lai, các công cụ như jazzy có thể tạo tài liệu để người khác có thể làm theo.

Đánh dấu các phần của mã

Việc sử dụng MARK có thể hữu ích để tách các phần mã. Nó cũng nhóm các chức năng độc đáo trong Thanh điều hướng. Bạn cũng có thể sử dụng các nhóm mở rộng, các thuộc tính và phương pháp liên quan.

Đối với một UIViewContoder đơn giản, chúng ta có thể xác định các Dấu hiệu sau:

// Đánh dấu: - Ban đầu
// Đánh dấu: - Xem vòng đời
// Đánh dấu: - Cài đặt
// Đánh dấu: - Hành động
// Đánh dấu: - Dữ liệu

Kiểm soát nguồn

Git là một hệ thống kiểm soát nguồn phổ biến ngay bây giờ. Chúng tôi có thể sử dụng tệp .gitignore mẫu từ gitignore.io/api/swift. Có cả ưu và nhược điểm trong việc kiểm tra các tệp phụ thuộc (CocoaPods và Carthage). Nó phụ thuộc vào dự án của bạn, nhưng tôi có xu hướng không cam kết phụ thuộc (node_modules, Carthage, Pods) trong điều khiển nguồn để không làm lộn xộn cơ sở mã. Nó cũng làm cho việc xem xét các yêu cầu Pull dễ dàng hơn.

Cho dù bạn có kiểm tra trong thư mục Pods hay không, Podfile và Podfile.lock phải luôn được giữ dưới sự kiểm soát phiên bản.

Tôi sử dụng cả iTerm2 để thực thi các lệnh và Cây nguồn để xem các nhánh và dàn dựng.

Phụ thuộc

Tôi đã sử dụng các khung công tác của bên thứ ba, và cũng đã thực hiện và đóng góp cho nguồn mở rất nhiều. Sử dụng một khung công tác mang lại cho bạn một sự thúc đẩy khi bắt đầu, nhưng nó cũng có thể hạn chế bạn rất nhiều trong tương lai. Có thể có một số thay đổi nhỏ mà rất khó để làm việc xung quanh. Điều tương tự xảy ra khi sử dụng SDK. Sở thích của tôi là chọn các khung nguồn mở đang hoạt động. Đọc mã nguồn và kiểm tra các khung cẩn thận và tham khảo ý kiến ​​với nhóm của bạn nếu bạn có kế hoạch sử dụng chúng. Một chút thận trọng không có hại.

Trong ứng dụng này, tôi cố gắng sử dụng càng ít phụ thuộc càng tốt. Chỉ cần đủ để chứng minh làm thế nào để quản lý phụ thuộc. Một số nhà phát triển có kinh nghiệm có thể thích Carthage, một người quản lý phụ thuộc vì nó mang lại cho bạn quyền kiểm soát hoàn toàn. Ở đây tôi chọn CocoaPods vì nó dễ sử dụng và cho đến nay nó vẫn hoạt động rất tốt.

Có một tệp có tên là .swift-phiên bản của giá trị 4.1 trong thư mục gốc của dự án để thông báo cho Cốc Cốc rằng dự án này sử dụng Swift 4.1. Điều này có vẻ đơn giản nhưng tôi đã mất khá nhiều thời gian để tìm ra.

Bắt tay vào dự án

Hãy để craft craft một số hình ảnh và biểu tượng khởi động để cung cấp cho dự án một cái nhìn tốt đẹp.

API

Cách dễ dàng để tìm hiểu mạng iOS là thông qua các dịch vụ API miễn phí công khai. Ở đây tôi sử dụng food2fork. Bạn có thể đăng ký tài khoản tại http://food2fork.com/about/api. Có nhiều API tuyệt vời khác trong kho lưu trữ công khai này.

Nó tốt để giữ thông tin của bạn ở một nơi an toàn. Tôi sử dụng 1Password để tạo và lưu trữ mật khẩu của mình.

Trước khi chúng tôi bắt đầu mã hóa, hãy để trò chơi với các API để xem loại yêu cầu nào họ yêu cầu và phản hồi mà họ trả lại. Tôi sử dụng công cụ Mất ngủ để kiểm tra và phân tích các phản hồi API. Nó có nguồn mở, miễn phí và hoạt động rất tốt.

Màn hình khởi chạy

Ấn tượng đầu tiên rất quan trọng, Màn hình khởi động cũng vậy. Cách ưa thích là sử dụng LaunchScreen.storyboard thay vì hình ảnh Launch tĩnh.

Để thêm hình ảnh khởi chạy vào Danh mục tài sản, hãy mở LaunchScreen.storyboard, thêm UIImageView và ghim nó vào các cạnh của UIView. Chúng ta không nên ghim hình ảnh vào Vùng an toàn vì chúng ta muốn hình ảnh ở chế độ toàn màn hình. Ngoài ra, bỏ chọn bất kỳ lề nào trong các ràng buộc Bố cục tự động. Đặt contentMode của UIImageView là Aspect Fill để nó kéo dài với tỷ lệ khung hình chính xác.

Cấu hình bố cục trong LaunchScreen.

Biểu tượng ứng dụng

Một thực hành tốt là cung cấp tất cả các biểu tượng ứng dụng cần thiết cho từng thiết bị mà bạn hỗ trợ và cả cho các địa điểm như Thông báo, Cài đặt và Springboard. Đảm bảo mỗi hình ảnh không có pixel trong suốt, nếu không nó sẽ dẫn đến nền đen. Mẹo này là từ Nguyên tắc Giao diện Con người - Biểu tượng Ứng dụng.

Giữ nền đơn giản và tránh sự minh bạch. Hãy chắc chắn rằng biểu tượng của bạn mờ đục và không làm lộn xộn nền. Cung cấp cho nó một nền tảng đơn giản để nó không áp đảo các biểu tượng ứng dụng khác gần đó. Bạn không cần phải điền vào toàn bộ biểu tượng.

Chúng ta cần thiết kế hình ảnh vuông với kích thước lớn hơn 1024 x 1024 để mỗi hình ảnh có thể thu nhỏ thành hình ảnh nhỏ hơn. Bạn có thể làm điều này bằng tay, tập lệnh hoặc sử dụng ứng dụng IconGenerator nhỏ này mà tôi đã tạo.

Ứng dụng IconGenerator có thể tạo các biểu tượng cho iOS trong các ứng dụng iPhone, iPad, macOS và watchOS. Kết quả là AppIcon.appiconset mà chúng ta có thể kéo ngay vào Danh mục tài sản. Danh mục tài sản là cách để đi cho các dự án Xcode hiện đại.

Mã lint với SwiftLint

Bất kể chúng tôi phát triển dựa trên nền tảng nào, thật tốt khi có một kẻ nói dối để thực thi các quy ước nhất quán. Công cụ phổ biến nhất cho các dự án Swift là SwiftLint, được tạo ra bởi những người tuyệt vời tại Realm.

Để cài đặt nó, hãy thêm pod 'SwiftLint', '~> 0.25' vào Podfile. Đây cũng là một cách thực hành tốt để chỉ định phiên bản của các phụ thuộc để cài đặt pod thắng vô tình cập nhật lên phiên bản chính có thể phá vỡ ứng dụng của bạn. Sau đó thêm một .swiftlint.yml với cấu hình ưa thích của bạn. Một cấu hình mẫu có thể được tìm thấy ở đây.

Cuối cùng, thêm một cụm từ Run Script mới để thực thi swiftlint sau khi biên dịch.

Loại tài nguyên an toàn

Tôi sử dụng R.swift để quản lý tài nguyên một cách an toàn. Nó có thể tạo các lớp an toàn kiểu để truy cập phông chữ, chuỗi có thể bản địa hóa và màu sắc. Bất cứ khi nào chúng tôi thay đổi tên tệp tài nguyên, chúng tôi sẽ nhận được các lỗi biên dịch thay vì sự cố ngầm. Điều này ngăn chúng tôi suy luận với các tài nguyên đang tích cực sử dụng.

imageView.image = R.image.notFound ()

Cho tôi xem mã

Hãy để sâu vào mã, bắt đầu với mô hình, bộ điều khiển luồng và các lớp dịch vụ.

Thiết kế mô hình

Nghe có vẻ nhàm chán nhưng khách hàng chỉ là một cách đẹp hơn để thể hiện phản hồi API. Mô hình có lẽ là điều cơ bản nhất và chúng tôi sử dụng nó rất nhiều trong ứng dụng. Nó đóng một vai trò quan trọng như vậy nhưng có thể có một số lỗi rõ ràng liên quan đến các mô hình không đúng định dạng và các giả định về cách một mô hình nên được phân tích cú pháp cần được xem xét.

Chúng ta nên kiểm tra cho mọi mô hình của ứng dụng. Lý tưởng nhất, chúng ta cần kiểm tra tự động các mô hình từ các phản hồi API trong trường hợp mô hình đã thay đổi từ phụ trợ.

Bắt đầu từ Swift 4.0, chúng tôi có thể tuân thủ mô hình của mình thành Codable để dễ dàng tuần tự hóa đến và từ JSON. Mô hình của chúng tôi nên bất biến:

Công thức cấu trúc: Codable {
  hãy để nhà xuất bản: Chuỗi
  hãy để url: URL
  hãy để sourceUrl: Chuỗi
  hãy để id: Chuỗi
  để tiêu đề: Chuỗi
  hãy để imageUrl: Chuỗi
  hãy để SocialRank: Nhân đôi
  hãy để nhà xuất bảnUrl: URL
enum CodingKeys: String, CodingKey {
    nhà xuất bản trường hợp
    trường hợp url = "f2f_url"
    trường hợp nguồnUrl = "source_url"
    trường hợp id = "công thức_id"
    trường hợp tiêu đề
    trường hợp imageUrl = "image_url"
    trường hợp socialRank = "social_rank"
    trường hợp nhà xuất bảnUrl = "nhà xuất bản_url"
  }
}

Chúng tôi có thể sử dụng một số khung kiểm tra nếu bạn thích cú pháp ưa thích hoặc kiểu RSpec. Một số khung kiểm tra của bên thứ ba có thể có vấn đề. Tôi thấy XCTest đủ tốt.

nhập khẩu XCTest
@testable Bí quyết nhập khẩu
lớp RecipesTests: XCTestCase {
  func testParsing () ném {
    let json: [String: Any] = [
      "nhà xuất bản": "Two Peas and Pod của họ",
      "f2f_url": "http://food2fork.com/view/975e33",
      "Tiêu đề": "Bánh quy bơ đậu phộng không nướng sô cô la",
      "source_url": "http://www.twopeasandtheirpod.com/no-bake-chocolate-peanut-butter-pretzel-cookies/",
      "Formula_id": "975e33",
      "image_url": "http://static.food2fork.com/NoBakeChocolatePeanutButterPretzelCookies44147.jpg",
      "Social_rank": 99.99999999999974,
      "nhà xuất bản_url": "http://www.twopeasandtheirpod.com"
    ]
hãy để dữ liệu = thử JSONSerialization.data (withJSONObject: json, tùy chọn: [])
    hãy để bộ giải mã = ​​JSONDecoder ()
    hãy để công thức = thử decoder.decode (Recipe.elf, từ: data)
XCTAssertEqual (công thức.title, "Bánh quy bơ đậu phộng không nướng sô cô la")
    XCTAssertEqual (công thức.id, "975e33")
    XCTAssertEqual (công thức.url, URL (chuỗi: "http://food2fork.com/view/975e33")!)
  }
}

Điều hướng tốt hơn với FlowControll

Trước đây, tôi đã sử dụng La bàn như một công cụ định tuyến trong các dự án của mình, nhưng theo thời gian, tôi đã thấy rằng việc viết mã Định tuyến đơn giản cũng hoạt động.

FlowContoder được sử dụng để quản lý nhiều thành phần liên quan đến UIViewContoder thành một tính năng phổ biến. Bạn có thể muốn đọc FlowContoder và Điều phối viên cho các trường hợp sử dụng khác và để hiểu rõ hơn.

Có AppFlowContoder quản lý thay đổi rootViewContaptor. Bây giờ, nó khởi động RecipeFlowControll.

window = UIWindow (khung: UIScreen.main.bound)
cửa sổ? .rootViewCont điều khiển = appFlowCont điều khiển
cửa sổ? .makeKeyAndVisible ()
appFlowControll.start ()

RecipeFlowContoder quản lý (thực tế là) UINavestionControll, xử lý việc đẩy HomeViewControll, RecipesDetailViewCont điều khiển, SafariViewControll.

lớp cuối cùng RecipeFlowCont điều khiển: UINavestionControll {
  /// Bắt đầu dòng chảy
  bắt đầu () {
    let service = RecipesService (mạng: NetworkService ())
    hãy để bộ điều khiển = HomeViewCont kiểm (công thức dịch vụ: dịch vụ)
    viewControllers = [bộ điều khiển]
    control.select = {[yếu tự] công thức trong
      tự? .startDetail (công thức: công thức)
    }
  }
private func startDetail (công thức: Recipe) {}
  func startWeb riêng (url: URL) {}
}

UIViewControll có thể sử dụng ủy nhiệm hoặc đóng để thông báo cho FlowContoder về những thay đổi hoặc màn hình tiếp theo trong luồng. Đối với đại biểu có thể cần phải kiểm tra khi có hai trường hợp của cùng một lớp. Ở đây chúng tôi sử dụng đóng cửa cho đơn giản.

Giao diện tự động

Giao diện tự động đã xuất hiện từ iOS 5, nó trở nên tốt hơn mỗi năm. Mặc dù một số người vẫn gặp vấn đề với nó, chủ yếu là do sự nhầm lẫn giữa các ràng buộc và hiệu suất, nhưng cá nhân tôi, tôi thấy Auto Layout là đủ tốt.

Tôi cố gắng sử dụng Giao diện tự động càng nhiều càng tốt để tạo giao diện người dùng thích ứng. Chúng tôi có thể sử dụng các thư viện như Neo để thực hiện Bố cục tự động nhanh và khai báo. Tuy nhiên, trong ứng dụng này, chúng tôi sẽ chỉ sử dụng NSLayoutAnchor vì nó là từ iOS 9. Mã dưới đây được lấy cảm hứng từ Constraint. Hãy nhớ rằng Bố cục tự động ở dạng đơn giản nhất liên quan đến việc chuyển đổi các bản dịchAutoresizingMaskIntoConstraint và kích hoạt các ràng buộc isActive.

tiện ích mở rộng NSLayoutConstraint {
  kích hoạt func tĩnh (_ ràng buộc: [NSLayoutConstraint]) {
    các ràng buộc.forEach {
      ($ 0.firstItem là? UIView)?. DịchAutoresizingMaskIntoConstraint = false
      $ 0.isActive = đúng
    }
  }
}

Thực tế có nhiều công cụ bố trí khác có sẵn trên GitHub. Để hiểu được cái nào sẽ phù hợp để sử dụng, hãy xem LayoutFrameworkBenchmark.

Ngành kiến ​​trúc

Kiến trúc có lẽ là chủ đề được thổi phồng và thảo luận nhiều nhất. Tôi là một fan hâm mộ của khám phá kiến ​​trúc, bạn có thể xem thêm bài viết và khung về các kiến ​​trúc khác nhau ở đây.

Đối với tôi, tất cả các kiến ​​trúc và các mẫu xác định vai trò cho từng đối tượng và cách kết nối chúng. Hãy nhớ những nguyên tắc hướng dẫn này cho sự lựa chọn kiến ​​trúc của bạn:

  • gói gọn những gì khác nhau
  • ủng hộ thành phần trên thừa kế
  • chương trình để giao diện, không thực hiện

Sau khi chơi xung quanh với nhiều kiến ​​trúc khác nhau, có và không có Rx, tôi phát hiện ra rằng MVC đơn giản là đủ tốt. Trong dự án đơn giản này, chỉ có UIViewControll với logic được gói gọn trong các lớp Service của trình trợ giúp,

Bộ điều khiển xem lớn

Bạn có thể đã nghe mọi người nói đùa về mức độ lớn của UIViewContoder, nhưng trong thực tế, không có bộ điều khiển xem lớn. Nó chỉ là chúng tôi viết mã xấu. Tuy nhiên có nhiều cách để làm thon nó xuống.

Trong ứng dụng công thức nấu ăn tôi sử dụng,

  • Dịch vụ đưa vào bộ điều khiển xem để thực hiện một tác vụ
  • Chế độ xem chung để di chuyển chế độ xem và điều khiển khai báo sang lớp Xem
  • Trình điều khiển khung nhìn con để soạn các trình điều khiển khung nhìn con để xây dựng nhiều tính năng hơn

Dưới đây là một bài viết rất hay với 8 mẹo để giảm nhẹ bộ điều khiển lớn.

Kiểm soát truy cập

Tài liệu SWIFT đề cập rằng kiểm soát truy cập của Viking hạn chế quyền truy cập vào các phần của mã của bạn từ mã trong các tệp và mô-đun nguồn khác. Tính năng này cho phép bạn ẩn chi tiết triển khai mã của mình và chỉ định giao diện ưa thích thông qua đó mã có thể được truy cập và sử dụng.

Tất cả mọi thứ nên được riêng tư và cuối cùng theo mặc định. Điều này cũng giúp trình biên dịch. Khi nhìn thấy một tài sản công cộng, chúng ta cần tìm kiếm nó trên toàn dự án trước khi làm bất cứ điều gì thêm với nó. Nếu tài sản chỉ được sử dụng trong một lớp, làm cho nó riêng tư có nghĩa là chúng ta không cần quan tâm nếu nó bị phá vỡ ở nơi khác.

Khai báo tài sản là cuối cùng nếu có thể.

lớp cuối cùng HomeViewCont Bộ điều khiển: UIViewControll {}

Khai báo các thuộc tính là riêng tư hoặc ít nhất là riêng tư (bộ).

lớp cuối cùng RecipeDetailView: UIView {
  riêng tư cho phép scrollableView = ScrollableView ()
  private (set) lazy var imageView: UIImageView = self.makeImageView ()
}

Tính chất lười biếng

Đối với các thuộc tính có thể được truy cập sau đó, chúng ta có thể khai báo chúng là lazyand có thể sử dụng bao đóng để xây dựng nhanh.

lớp cuối cùng RecipeCell: UICollectionViewCell {
  private (set) lazy var containerView: UIView = {
    hãy xem = UIView ()
    view.clipsToBound = true
    view.layer.cornerRadius = 5
    view.backgroundColor = Color.main.withAlphaComponent (0.4)
xem lại
  } ()
}

Chúng ta cũng có thể sử dụng hàm make nếu chúng ta dự định sử dụng lại cùng một hàm cho nhiều thuộc tính.

lớp cuối cùng RecipeDetailView: UIView {
  private (set) lazy var imageView: UIImageView = self.makeImageView ()
func makeImageView () -> UIImageView {
    hãy để imageView = UIImageView ()
    imageView.contentMode = .scaleAspectFill
    imageView.clipsToBound = true
    hình ảnh trở lại
  }
}

Điều này cũng phù hợp với lời khuyên từ Strive for Fluent Use.

Bắt đầu tên của các phương thức xuất xưởng với dòng chữ Make make, Ví dụ: x.makeIterator ().

Đoạn mã

Một số cú pháp mã là khó nhớ. Xem xét sử dụng đoạn mã để tự động tạo mã. Điều này được hỗ trợ bởi Xcode và là cách ưa thích của các kỹ sư Apple khi họ demo.

nếu #av Available (iOS 11, *) {
  viewControll.navlationItem.searchControll = searchControll
  viewControll.navlationItem.hidesSearchBarWhenScrolling = false
} khác {
  viewControll.navlationItem.titleView = searchControll.searchBar
}

Tôi đã tạo một repo với một số đoạn Swift hữu ích mà nhiều người thích sử dụng.

Mạng

Mạng trong Swift là một vấn đề được giải quyết. Có những nhiệm vụ tẻ nhạt và dễ bị lỗi như phân tích phản hồi HTTP, xử lý hàng đợi yêu cầu, xử lý các truy vấn tham số. Tôi đã thấy các lỗi về các yêu cầu PATCH, các phương thức HTTP được hạ cấp, chúng tôi chỉ có thể sử dụng Alamofire. Ở đó, không cần phải lãng phí thời gian ở đây.

Đối với ứng dụng này, vì nó đơn giản và để tránh các phụ thuộc không cần thiết. Chúng tôi chỉ có thể sử dụng URLSession trực tiếp. Một tài nguyên thường chứa URL, đường dẫn, tham số và phương thức HTTP.

cấu trúc tài nguyên {
  hãy để url: URL
  cho đường dẫn: Chuỗi?
  hãy để httpMethod: Chuỗi
  hãy để tham số: [String: String]
}

Một dịch vụ mạng đơn giản có thể phân tích tài nguyên thành URLRequest và yêu cầu URLSession thực thi

lớp cuối cùng Dịch vụ mạng: Mạng {
  @discardableResult func fetch (tài nguyên: Tài nguyên, hoàn thành: @escaping (Dữ liệu?) -> Vô hiệu) -> URLSessionTask? {
    bảo vệ let request = makeRequest (resource: resource) other {
      hoàn thành (không)
      trở về
    }
let task = session.dataTask (với: request, xongHandler: {data, _, lỗi trong
      bảo vệ dữ liệu = dữ liệu, lỗi == nil khác {
        hoàn thành (không)
        trở về
      }
hoàn thành (dữ liệu)
    })
task.resume ()
    nhiệm vụ trở lại
  }
}

Sử dụng tiêm phụ thuộc. Cho phép người gọi chỉ định URLSessionConfiguration. Ở đây chúng tôi sử dụng tham số mặc định Swift để cung cấp tùy chọn phổ biến nhất.

init (cấu hình: URLSessionConfiguration = URLSessionConfiguration.default) {
  self.session = URLSession (cấu hình: cấu hình)
}

Tôi cũng sử dụng URLQueryItem từ iOS 8. Nó giúp phân tích các tham số để truy vấn các mục tốt đẹp và ít tẻ nhạt hơn.

Cách kiểm tra mã mạng

Chúng tôi có thể sử dụng URLProtocol và URLCache để thêm sơ khai cho các phản hồi của mạng hoặc chúng tôi có thể sử dụng các khung như Mockingjay làm thay đổi URLSessionConfiguration.

Bản thân tôi thích sử dụng giao thức để kiểm tra. Bằng cách sử dụng giao thức, thử nghiệm có thể tạo một yêu cầu giả để cung cấp phản hồi còn sơ khai.

Giao thức Mạng {
  @discardableResult func fetch (tài nguyên: Tài nguyên, hoàn thành: @escaping (Dữ liệu?) -> Vô hiệu) -> URLSessionTask?
}
lớp cuối cùng MockNetworkService: Mạng {
  để dữ liệu: Dữ liệu
  init (tên tệp: Chuỗi) {
    let bundle = Bundle (for: MockNetworkService.elf)
    hãy để url = bundle.url (forResource: fileName, withExtension: "json")!
    self.data = thử! Dữ liệu (nội dungOf: url)
  }
func fetch (tài nguyên: Tài nguyên, hoàn thành: @escaping (Dữ liệu?) -> Vô hiệu) -> URLSessionTask? {
    hoàn thành (dữ liệu)
    trở về
  }
}

Triển khai bộ đệm để hỗ trợ ngoại tuyến

Tôi đã từng đóng góp và sử dụng một thư viện có tên Cache rất nhiều. Những gì chúng ta cần từ một thư viện bộ đệm tốt là bộ nhớ cache và bộ nhớ cache, bộ nhớ để truy cập nhanh, đĩa để duy trì. Khi chúng ta lưu, chúng ta lưu vào cả bộ nhớ và đĩa. Khi chúng tôi tải, nếu bộ nhớ cache bị lỗi, chúng tôi tải từ đĩa, sau đó cập nhật lại bộ nhớ. Có nhiều chủ đề nâng cao về bộ nhớ cache như thanh trừng, hết hạn, tần suất truy cập. Có một đọc về họ ở đây.

Trong ứng dụng đơn giản này, một lớp dịch vụ bộ đệm trong nhà là đủ và là một cách tốt để tìm hiểu cách thức hoạt động của bộ đệm. Mọi thứ trong Swift có thể được chuyển đổi thành Dữ liệu, vì vậy chúng tôi chỉ có thể lưu Dữ liệu vào bộ đệm. Swift 4 Codable có thể tuần tự hóa đối tượng thành Dữ liệu.

Đoạn mã dưới đây cho chúng ta thấy cách sử dụng FileManager cho bộ đệm đĩa.

/// Lưu và tải dữ liệu vào bộ nhớ và bộ nhớ cache của đĩa
lớp cuối cùng CacheService {
/// Để lấy hoặc tải dữ liệu trong bộ nhớ
  riêng tư cho bộ nhớ = NSCache  ()
/// Url đường dẫn chứa các tệp được lưu trong bộ nhớ cache (tệp mp3 và tệp hình ảnh)
  riêng tư cho đĩaPath: URL
/// Để kiểm tra tệp hoặc thư mục tồn tại trong một đường dẫn cụ thể
  private let fileManager: FileManager
/// Hãy chắc chắn rằng tất cả các hoạt động được thực hiện ser seri
  private let serialQueue = DispatchQueue (nhãn: "Bí quyết")
init (fileManager: FileManager = FileManager.default) {
    self.fileManager = fileQuản lý
    làm {
      hãy để documentDirectory = thử fileManager.url (
        cho: .documentDirectory,
        trong: .userDomainMask,
        thích hợp: không,
        tạo ra: đúng
      )
      đĩaPath = documentDirectory.appendingPathComponent ("Bí quyết")
      hãy thử tạoDirectory IfNeeded ()
    } nắm lấy {
      lỗi nghiêm trọng()
    }
  }
func save (dữ liệu: Dữ liệu, khóa: Chuỗi, hoàn thành: (() -> Void)? = nil) {
    đặt khóa = MD5 (khóa)
serialQueue.async {
      self.memory.setObject (dữ liệu dưới dạng NSData, forKey: key là NSString)
      làm {
        thử data.write (đến: self.filePath (key: key))
        hoàn thành?()
      } nắm lấy {
        in (lỗi)
      }
    }
  }
}

Để tránh tên tệp không đúng và rất dài, chúng ta có thể băm chúng. Tôi sử dụng MD5 từ SwiftHash, cho phép sử dụng đơn giản let key = MD5 (key).

Cách kiểm tra Cache

Vì tôi thiết kế các hoạt động Cache không đồng bộ, chúng tôi cần sử dụng kỳ vọng kiểm tra. Nhớ đặt lại trạng thái trước mỗi thử nghiệm để trạng thái thử nghiệm trước đó không can thiệp vào thử nghiệm hiện tại. Sự mong đợi trong XCTestCase giúp việc kiểm tra mã không đồng bộ dễ dàng hơn bao giờ hết.

lớp CacheServiceTests: XCTestCase {
  hãy để dịch vụ = CacheService ()
ghi đè func setUp () {
    super.setUp ()
thử? dịch vụ.clear ()
  }
func testClear () {
    hãy kỳ vọng = self.recectation (mô tả: #feft)
    hãy để chuỗi = "Xin chào thế giới"
    hãy để dữ liệu = string.data (sử dụng: .utf8)!
service.save (dữ liệu: dữ liệu, khóa: "khóa", hoàn thành: {
      thử? self.service.clear ()
      self.service.load (khóa: "khóa", hoàn thành: {
        XCTAssertNil ($ 0)
        mong đợi.fulfill ()
      })
    })
chờ đợi (cho: [kỳ vọng], thời gian chờ: 1)
  }
}

Đang tải hình ảnh từ xa

Tôi cũng đóng góp cho Tưởng tượng để tôi biết một chút về cách thức hoạt động của nó. Đối với hình ảnh từ xa, chúng ta cần tải xuống và lưu trữ nó và khóa bộ đệm thường là URL của hình ảnh từ xa.

Trong ứng dụng người nhận của chúng tôi, hãy để xây dựng một ImageService đơn giản dựa trên NetworkService và CacheService của chúng tôi. Về cơ bản một hình ảnh chỉ là một tài nguyên mạng mà chúng tôi tải xuống và lưu trữ. Chúng tôi thích thành phần vì vậy chúng tôi sẽ bao gồm NetworkService và CacheService vào ImageService.

/// Kiểm tra bộ đệm cục bộ và tìm nạp hình ảnh từ xa
lớp cuối cùng ImageService {
riêng tư cho phép dịch vụ mạng: Mạng
  riêng tư hãy để cacheService: CacheService
  Nhiệm vụ var riêng tư: URLSessionTask?
init (mạngService: Mạng, cacheService: CacheService) {
    self.networkService = networkService
    self.cacheService = cacheService
  }
}

Chúng tôi thường có các ô UICollectionView và UITableView với UIImageView. Và vì các ô được sử dụng lại, chúng tôi cần hủy mọi tác vụ yêu cầu hiện có trước khi đưa ra yêu cầu mới.

func fetch (url: URL, hoàn thành: @escaping (UIImage?) -> Void) {
  // Hủy tác vụ hiện có nếu có
  nhiệm vụ? .celon ()
// Thử tải từ bộ đệm
  cacheService.load (khóa: url.absoluteString, hoàn thành: {[tự yếu] cacheedData trong
    if let data = cacheedData, let image = UIImage (data: data) {
      DispatchQueue.main.async {
        hoàn thành (hình ảnh)
      }
    } khác {
      // Thử yêu cầu từ mạng
      hãy để tài nguyên = Tài nguyên (url: url)
      self? .task = self? .networkService.fetch (resource: resource, hoàn thành: {networkData in
        if let data = networkData, let image = UIImage (data: data) {
          // Lưu vào bộ đệm
          tự? .cacheService.save (dữ liệu: dữ liệu, khóa: url.absoluteString)
          DispatchQueue.main.async {
            hoàn thành (hình ảnh)
          }
        } khác {
          in ("Lỗi khi tải hình ảnh tại \ (url)")
        }
      })
tự? .task? .resume ()
    }
  })
}

Giúp tải hình ảnh thuận tiện hơn cho UIImageView

Hãy để thêm một phần mở rộng cho UIImageView để đặt hình ảnh từ xa từ URL. Tôi sử dụng đối tượng liên quan để giữ ImageService này và để hủy các yêu cầu cũ. Chúng tôi sử dụng tốt đối tượng được liên kết để đính kèm ImageService vào UIImageView. Vấn đề là hủy yêu cầu hiện tại khi yêu cầu được kích hoạt lại. Điều này rất hữu ích khi các chế độ xem hình ảnh được hiển thị trong danh sách cuộn.

tiện ích mở rộng UIImageView {
  func setImage (url: URL, giữ chỗ: UIImage? = nil) {
    if imageService == nil {
      imageService = ImageService (networkService: NetworkService (), cacheService: CacheService ())
    }
self.image = giữ chỗ
    self.imageService? .fetch (url: url, hoàn thành: {[self self] hình ảnh trong
      tự? .image = hình ảnh
    })
  }
Dịch vụ hình ảnh var riêng tư: ImageService? {
    được {
      trả về objc_getAssociatedObject (tự, & AssociateKey.imageService) là? Dịch vụ hình ảnh
    }
    bộ {
      objc_setAssociatedObject (
        tự
        & AssociateKey.imageService,
        giá trị mới
        objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
      )
    }
  }
}

Nguồn dữ liệu chung cho UITableView và UICollectionView

Chúng tôi sử dụng UITableView và UICollectionView trong hầu hết mọi ứng dụng và gần như thực hiện cùng một việc nhiều lần.

  • hiển thị điều khiển làm mới trong khi tải
  • tải lại danh sách trong trường hợp dữ liệu
  • hiển thị lỗi trong trường hợp thất bại.

Có rất nhiều hàm bao quanh UITableView và UICollection. Mỗi lớp thêm một lớp trừu tượng khác, cho chúng ta nhiều sức mạnh hơn nhưng áp dụng các hạn chế cùng một lúc.

Trong ứng dụng này, tôi sử dụng Adaptor để lấy nguồn dữ liệu chung, để tạo một bộ sưu tập an toàn. Bởi vì, cuối cùng, tất cả những gì chúng ta cần là ánh xạ từ mô hình đến các ô.

Tôi cũng sử dụng ngược dòng dựa trên ý tưởng này. Nó khó có thể bao quanh UITableView và UICollectionView, vì nhiều lần, nó là ứng dụng cụ thể, do đó, một trình bao bọc mỏng như Adaptor là đủ.

Bộ điều hợp lớp cuối cùng : NSObject,
UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
  mục var: [T] = []
  cấu hình var: ((T, Cell) -> Void)?
  var chọn: ((T) -> Vô hiệu)?
  var cellHeight: CGFloat = 60
}

Bộ điều khiển và khung nhìn

Tôi đã bỏ Storyboard vì nhiều hạn chế và nhiều vấn đề. Thay vào đó, tôi sử dụng mã để tạo các khung nhìn và xác định các ràng buộc. Nó không phải là khó để làm theo. Hầu hết các mã soạn sẵn trong UIViewControll là để tạo các khung nhìn và cấu hình bố cục. Hãy di chuyển chúng để xem. Bạn có thể đọc về điều ấy nhiều hơn tại đây.

/// Được sử dụng để phân tách giữa bộ điều khiển và chế độ xem
lớp BaseContoder : UIViewControll {
  hãy để root = T ()
ghi đè func loadView () {
    xem = root
  }
}
lớp cuối cùng RecipeDetailViewCont Bộ điều khiển: BaseContoder  {}

Xử lý trách nhiệm với một đứa trẻ Xem điều khiển

Bộ điều khiển View container là một khái niệm mạnh mẽ. Mỗi bộ điều khiển khung nhìn có một mối quan tâm riêng biệt và có thể được kết hợp với nhau để tạo ra các tính năng nâng cao. Tôi đã sử dụng RecipeListViewControll để quản lý UICollectionView và hiển thị danh sách các công thức nấu ăn.

lớp cuối cùng RecipeListViewCont điều khiển: UIViewControll {
  private (set) var CollectionView: UICollectionView!
  let adapter = Adaptor  ()
  private let blankView = EmptyView (văn bản: "Không tìm thấy công thức nấu ăn!")
}

Có HomeViewContaptor nhúng RecipeListViewControll này

/// Hiển thị danh sách các công thức nấu ăn
lớp cuối cùng HomeViewCont Bộ điều khiển: UIViewControll {
/// Khi một công thức được chọn
  var chọn: ((Công thức) -> Vô hiệu)?
private var refreshControl = UIRefreshControl ()
  riêng tư cho công thức nấu ăn Dịch vụ: RecipesService
  riêng tư hãy tìm kiếmComponent: SearchComponent
  riêng tư hãy để FormulaListViewControll = RecipeListViewCont điều khiển ()
}

Thành phần và tiêm phụ thuộc

Tôi cố gắng xây dựng các thành phần và soạn mã bất cứ khi nào tôi có thể. Chúng tôi thấy rằng ImageService sử dụng NetworkService và CacheService và RecipeDetailViewCont kiểm soát sử dụng Recipe và RecipesService

Đối tượng lý tưởng không nên tự tạo ra sự phụ thuộc. Các phụ thuộc nên được tạo ra bên ngoài và truyền từ gốc. Trong ứng dụng của chúng tôi, root là AppDelegate và AppFlowControll, vì vậy các phụ thuộc sẽ bắt đầu từ đây.

Ứng dụng bảo mật vận tải

Kể từ iOS 9, tất cả các ứng dụng sẽ áp dụng App Transport Security

Ứng dụng bảo mật vận chuyển (ATS) thực thi các thực tiễn tốt nhất trong các kết nối an toàn giữa ứng dụng và mặt sau của ứng dụng. ATS ngăn chặn tiết lộ ngẫu nhiên, cung cấp hành vi mặc định an toàn và dễ dàng chấp nhận; nó cũng được bật theo mặc định trong iOS 9 và OS X v10.11. Bạn nên áp dụng ATS càng sớm càng tốt, bất kể bạn có đang tạo một ứng dụng mới hay cập nhật một ứng dụng hiện có hay không.

Trong ứng dụng của chúng tôi, một số hình ảnh thu được qua kết nối HTTP. Chúng ta cần loại trừ nó khỏi quy tắc bảo mật, nhưng chỉ cho tên miền đó.

 NSAppTransportSecurance 

   NSExceptionDomains 
  
     food2fork.com 
    
       NSIncludesSubdomains 
      <đúng />
       NSException ALLowsInsecureHTTPLoads 
      <đúng />
    
  

Chế độ xem có thể cuộn tùy chỉnh

Đối với màn hình chi tiết, chúng ta có thể sử dụng UITableView và UICollectionView với các loại ô khác nhau. Ở đây, các khung nhìn nên tĩnh. Chúng tôi có thể xếp chồng bằng UIStackView. Để linh hoạt hơn, chúng ta chỉ cần sử dụng UIScrollView.

/// Chế độ xem bố cục theo chiều dọc bằng cách sử dụng Bố cục tự động trong UIScrollView
lớp cuối cùng ScrollableView: UIView {
  riêng tư hãy cuộn scrollView = UIScrollView ()
  riêng tư hãy để contentView = UIView ()
ghi đè init (khung: CGRect) {
    super.init (khung: khung)
scrollView.showsHTHERScrollTheicator = false
    scrollView.alwaysBounceHTHER = false
    addSubview (scrollView)
scrollView.addSubview (contentView)
NSLayoutConstraint.activate ([
      scrollView.topAnchor.constraint (bằngTo: topAnchor),
      scrollView.bottomAnchor.constraint (bằngTo: bottomAnchor),
      scrollView.leftAnchor.constraint (bằngTo: leftAnchor),
      scrollView.rightAnchor.constraint (bằngTo: rightAnchor),
contentView.topAnchor.constraint (bằngTo: scrollView.topAnchor),
      contentView.bottomAnchor.constraint (bằngTo: scrollView.bottomAnchor),
      contentView.leftAnchor.constraint (bằngTo: leftAnchor),
      contentView.rightAnchor.constraint (bằngTo: rightAnchor)
    ])
  }
}

Chúng tôi ghim UIScrollView vào các cạnh. Chúng tôi ghim nội dung neo bên trái và bên phải vào chính mình, trong khi ghim neo nội dung trên cùng và dưới cùng vào UIScrollView.

Các khung nhìn bên trong contentView có các ràng buộc trên và dưới, vì vậy khi chúng mở rộng, chúng cũng mở rộng contentView. UIScrollView sử dụng thông tin Bố cục tự động từ nội dung này để xác định kích thước nội dung của nó. Đây là cách ScrollableView được sử dụng trong RecipeDetailView.

scrollableView.setup (cặp: [
  ScrollableView.Pair (xem: imageView, inet: UIEdgeInsets (top: 8, left: 0, bottom: 0, right: 0)),
  ScrollableView.Pair (view: thành phầnHeadView, inet: UIEdgeInsets (top: 8, left: 0, bottom: 0, right: 0)),
  ScrollableView.Pair (xem: thành phầnLabel, inet: UIEdgeInsets (top: 4, left: 8, bottom: 0, right: 0)),
  ScrollableView.Pair (view: infoHeaderView, inet: UIEdgeInsets (top: 4, left: 0, bottom: 0, right: 0)),
  ScrollableView.Pair (xem: hướng dẫnButton, inet: UIEdgeInsets (top: 8, left: 20, bottom: 0, right: 20)),
  ScrollableView.Pair (xem: gốcButton, inet: UIEdgeInsets (trên cùng: 8, bên trái: 20, dưới cùng: 0, phải: 20)),
  ScrollableView.Pair (xem: thông tin, inet: UIEdgeInsets (top: 16, left: 0, bottom: 20, right: 0))
])

Thêm chức năng tìm kiếm

Từ iOS 8 trở đi, chúng ta có thể sử dụng UISearchControll để có trải nghiệm tìm kiếm mặc định với thanh tìm kiếm và bộ điều khiển kết quả. Chúng tôi sẽ đóng gói chức năng tìm kiếm vào SearchComponent để có thể cắm được.

lớp cuối cùng SearchComponent: NSObject, UISearchResultsUpdating, UISearchBarDelegate {
  hãy để công thức nấu ăn Dịch vụ: RecipesService
  hãy để searchControll: UISearchControll
  hãy để FormulaListViewControll = RecipeListViewControll ()
}

Bắt đầu từ iOS 11, có một thuộc tính được gọi là searchContoder trên UINavestionItem giúp dễ dàng hiển thị thanh tìm kiếm trên thanh điều hướng.

func add (để xemControll: UIViewControll) {
  nếu #av Available (iOS 11, *) {
    viewControll.navlationItem.searchControll = searchControll
    viewControll.navlationItem.hidesSearchBarWhenScrolling = false
  } khác {
    viewControll.navlationItem.titleView = searchControll.searchBar
  }
viewControll.definesPftimeationContext = true
}

Trong ứng dụng này, chúng ta cần vô hiệu hóa hidesNavulationBarDuringPftimeation vì nó khá lỗi. Hy vọng nó sẽ được giải quyết trong các bản cập nhật iOS trong tương lai.

Hiểu ngữ cảnh trình bày

Hiểu ngữ cảnh trình bày là rất quan trọng để xem trình bày bộ điều khiển xem. Trong tìm kiếm, chúng tôi sử dụng searchResultsControll.

self.searchControll = UISearchCont điều khiển (searchResultsCont điều khiển: FormulaListViewCont kiểm)

Chúng ta cần sử dụng định nghĩaPftimeationContext trên bộ điều khiển xem nguồn (bộ điều khiển xem nơi chúng ta thêm thanh tìm kiếm vào). Nếu không có điều này, chúng ta sẽ tìm kiếm trình duyệtResultsControll trên toàn màn hình !!!

Khi sử dụng kiểu currentContext hoặc overCienContext để trình bày bộ điều khiển khung nhìn, thuộc tính này điều khiển bộ điều khiển khung nhìn hiện có trong hệ thống phân cấp bộ điều khiển xem của bạn thực sự được bao phủ bởi nội dung mới. Khi một bản trình bày dựa trên ngữ cảnh xảy ra, UIKit bắt đầu ở trình điều khiển khung nhìn trình bày và đi lên phân cấp trình điều khiển khung nhìn. Nếu nó tìm thấy bộ điều khiển khung nhìn có giá trị cho thuộc tính này là đúng, nó yêu cầu bộ điều khiển khung nhìn đó trình bày bộ điều khiển khung nhìn mới. Nếu không có trình điều khiển chế độ xem xác định bối cảnh bản trình bày, UIKit sẽ yêu cầu cửa sổ Bộ điều khiển chế độ xem gốc root xử lý bản trình bày.
Giá trị mặc định cho thuộc tính này là sai. Một số bộ điều khiển xem do hệ thống cung cấp, chẳng hạn như UINavestionContaptor, thay đổi giá trị mặc định thành true.

Hành động tìm kiếm

Chúng ta không nên thực hiện các yêu cầu tìm kiếm cho mỗi lần nhấn phím loại người dùng trong thanh tìm kiếm. Do đó một số loại điều tiết là cần thiết. Chúng ta có thể sử dụng DispatchWorkItem để đóng gói hành động và gửi nó đến hàng đợi. Sau này chúng ta có thể hủy bỏ nó.

lớp cuối cùng Debouncer {
  riêng tư để trì hoãn: TimeInterval
  private var workItem: DispatchWorkItem?
init (độ trễ: TimeInterval) {
    self.delay = trì hoãn
  }
/// Kích hoạt hành động sau một số độ trễ
  lịch trình func (hành động: @escaping () -> Vô hiệu) {
    workItem? .celon ()
    workItem = DispatchWorkItem (khối: hành động)
    DispatchQueue.main.asyncAfter (hạn chót: .now () + delay, thực thi: workItem!)
  }
}

Thử nghiệm thảo luận với kỳ vọng đảo ngược

Để kiểm tra Debouncer, chúng ta có thể sử dụng kỳ vọng XCTest ở chế độ đảo ngược. Đọc thêm về nó trong Đơn vị kiểm tra mã Swift không đồng bộ.

Để kiểm tra xem tình huống không xảy ra trong quá trình thử nghiệm, hãy tạo một kỳ vọng được hoàn thành khi tình huống bất ngờ xảy ra và đặt thuộc tính isInverted của nó thành true. Thử nghiệm của bạn sẽ thất bại ngay lập tức nếu kỳ vọng đảo ngược được thực hiện.
lớp DebouncerTests: XCTestCase {
  func testDebouncing () {
    hãy hủyExpectation = self.recectation (mô tả: "hủy")
    hủyExpectation.isInverted = true
hãy hoàn thànhExpectation = self.recectation (mô tả: "hoàn thành")
    let debouncer = Debouncer (delay: 0.3)
debouncer.schedule {
      hủyExpectation.fulfill ()
    }
debouncer.schedule {
      hoàn thànhExpectation.fulfill ()
    }
chờ đợi (cho: [hủyExpectation, CompleteExpectation], thời gian chờ: 1)
  }
}

Kiểm tra giao diện người dùng với UITests

Đôi khi tái cấu trúc nhỏ có thể có hiệu ứng lớn. Một nút bị vô hiệu hóa có thể dẫn đến màn hình không thể sử dụng sau đó. UITest giúp đảm bảo tính toàn vẹn và các khía cạnh chức năng của ứng dụng. Kiểm tra nên được khai báo. Chúng ta có thể sử dụng mô hình Robot.

lớp RecipesUITests: XCTestCase {
  ứng dụng var: XCUIApplication!
  ghi đè func setUp () {
    super.setUp ()
    continueAfterFailure = false
    ứng dụng = XCUIApplication ()
  }
  func testScrolling () {
    app.launch ()
    hãy để bộ sưu tậpView = app.collectionViews.element (ràng buộcBy: 0)
    bộ sưu tậpView.swipeUp ()
    bộ sưu tậpView.swipeUp ()
  }
  func testGoToDetail () {
    app.launch ()
    hãy để bộ sưu tậpView = app.collectionViews.element (ràng buộcBy: 0)
    hãy để FirstCell = sưu tậpView.cells.element (ràng buộcBy: 0)
    FirstCell.tap ()
  }
}

Dưới đây là một số bài viết của tôi liên quan đến thử nghiệm.

  • Chạy UITests với đăng nhập Facebook trong iOS
  • Thử nghiệm trong Swift với mẫu Đưa ra khi đó

Bảo vệ chủ đề chính

Truy cập UI từ hàng đợi nền có thể dẫn đến các vấn đề tiềm ẩn. Trước đây, tôi cần sử dụng MainThreadGuard, bây giờ Xcode 9 có Trình kiểm tra luồng chính, tôi chỉ kích hoạt tính năng đó trong Xcode.

Trình kiểm tra luồng chính là một công cụ độc lập cho các ngôn ngữ Swift và C phát hiện việc sử dụng AppKit, UIKit và các API khác không hợp lệ trên luồng nền. Cập nhật giao diện người dùng trên một luồng khác với luồng chính là một lỗi phổ biến có thể dẫn đến việc cập nhật giao diện người dùng bị bỏ lỡ, khiếm khuyết thị giác, hỏng dữ liệu và sự cố.

Đo lường hiệu suất và các vấn đề

Chúng tôi có thể sử dụng dụng cụ để hồ sơ kỹ lưỡng ứng dụng. Để đo nhanh, chúng ta có thể đi qua tab Debug Navigator và xem mức độ sử dụng CPU, Bộ nhớ và Mạng. Kiểm tra bài viết tuyệt vời này để tìm hiểu thêm về các công cụ.

Tạo mẫu với Sân chơi

Sân chơi là cách được đề xuất để tạo nguyên mẫu và xây dựng ứng dụng. Tại WWDC 2018, Apple đã giới thiệu Tạo ML hỗ trợ Playground để đào tạo mô hình. Kiểm tra bài viết thú vị này để tìm hiểu thêm về phát triển theo hướng sân chơi trong Swift.

Đi đâu từ đây

Cảm ơn đã làm cho nó đến nay. Tôi hy vọng bạn đã học được một cái gì đó hữu ích. Cách tốt nhất để học một cái gì đó là chỉ cần làm điều đó. Nếu bạn tình cờ viết cùng một mã nhiều lần, hãy biến nó thành một thành phần. Nếu một vấn đề khiến bạn gặp khó khăn, hãy viết về nó. Chia sẻ kinh nghiệm của bạn với thế giới, bạn sẽ học được rất nhiều.

Tôi khuyên bạn nên kiểm tra bài viết Những nơi tốt nhất để tìm hiểu phát triển iOS để tìm hiểu thêm về phát triển iOS.

Nếu bạn có bất kỳ câu hỏi, nhận xét hoặc phản hồi nào, đừng quên thêm chúng vào phần bình luận. Và nếu bạn thấy bài viết này hữu ích, thì hãy quên vỗ tay.

Nếu bạn thích bài đăng này, hãy xem xét việc truy cập các bài viết và ứng dụng khác của tôi