As usual, let’s show you the results:



# Technical route

Tripartite library selection:

  • Texture
  • Moya
  • RxSwift
  • Kingfisher
  • Lantern: Photo browser
  • Toast-Swift: toast prompt
  • etc…

# Overall architecture

Texture interface element setup, RxSwift VM data stream driven View update, Moya + Rx network request.

# Texture

Getting started with Texture wasn’t as difficult as I thought it would be. There are plenty of examples from the official community, as well as a few good tech blogs that give us a good shot at it.

Layout: FlexBox layout can be played on the web (ASDK🐸).

Principle and application:

  • Technology team AsyncDisplayKit introduction

  • Preloading and intelligent preloading

  • Official documentation, source code and official demo

  • The source code accompanying this article

Here’s a layout for a list of thumbs-up images:


// Title
private lazy var titleNode: ASTextNode = {
    let node = ASTextNode(a)let attr: [NSAttributedString.Key: Any] = [
        .font: UIFont.systemFont(ofSize: 12, weight: .regular),
        .foregroundColor: UIColor.XiTu.digCount
    ]
    node.attributedText = NSAttributedString(string: "Waiting to be liked.", attributes: attr)
    return node
}()

/ / layout
override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {

        let imageHeihgt = constrainedSize.min.height - 1
        imageNode1.style.preferredSize = CGSize(width: imageHeihgt, height: imageHeihgt)
        imageNode2.style.preferredSize = CGSize(width: imageHeihgt, height: imageHeihgt)
        imageNode3.style.preferredSize = CGSize(width: imageHeihgt, height: imageHeihgt)
        imageNode1.cornerRadius = imageHeihgt / 2
        imageNode2.cornerRadius = imageHeihgt / 2
        imageNode3.cornerRadius = imageHeihgt / 2

        let overlayWidth: CGFloat = 5
        let inset1 = ASInsetLayoutSpec(insets: .only(.right, value: 2 * (imageHeihgt - overlayWidth)), child: imageNode1)
        let inset2 = ASInsetLayoutSpec(insets: .only(.horizontal, value: imageHeihgt - overlayWidth), child: imageNode2)
        let inset3 = ASInsetLayoutSpec(insets: .only(.left, value:2 * (imageHeihgt - overlayWidth) ), child: imageNode3)
        let overlay1 = ASOverlayLayoutSpec(child: inset1, overlay: inset2)
        let overlay = ASOverlayLayoutSpec(child: overlay1, overlay: inset3)

        let hStack = ASStackLayoutSpec.horizontal()
        hStack.spacing = 5
        hStack.alignItems = .center
        hStack.children = [overlay, titleNode]

        return ASInsetLayoutSpec(insets: .only(.left, value: 7), child: hStack)
    }
Copy the code

# MVVMRxSwift

The Demo does not use bidirectional data binding, operator overloading of RxSwift, and RxTableViewDataSource. Overall code readability should improve it, suitable for RxSwift entry learning.

ViewModel: VM layer uses protocol to distinguish input and output, interface isolation:

protocol DynamicListViewModelInputs {

    func viewDidLoad(a)

    func refreshDate(a)

    func moreData(with cursor: String)

    // FIXED: - The view layer interface needs to be FIXED according to the product logic. For example, whether to deal with some of the buried points and other additional operations
 
    /// view details
    func showDetail(a)

    /// View the likes
    func diggUserClick(a)
}

protocol DynamicListViewModelOutputs {

    //var willRefreshData: Observable<Void> { get }

    var refreshData: Observable<XTListResultModel> { get }

    var moreData: Observable<XTListResultModel> { get }

    var endRefresh: Observable<Void> { get }

    var hasMoreData: Observable<Bool> { get }

    var showError: Observable<String> { get}}protocol DynamicListViewModelType {
    var input: DynamicListViewModelInputs { get }
    var output: DynamicListViewModelOutputs { get}}final class DynamicListViewModel: DynamicListViewModelType.DynamicListViewModelInputs.DynamicListViewModelOutputs {

    var input: DynamicListViewModelInputs { self }
    var output: DynamicListViewModelOutputs { self }
    // ect...
}
Copy the code

Use in View(VC) :

extension DynamicListViewController {
    func eventListen(a) {
        self.mjHeader.refreshingBlock ={[unowned self] in
            self.viewModel.input.refreshDate()
        }

        self.mjFooter.refreshingBlock ={[unowned self] in
            self.viewModel.input.moreData(with: self.dataSource.nextCursor)
        }
    }
}

// MARK: - Bind viewModel

extension DynamicListViewController {

    func bindViewModel(a) {

        viewModel.output.refreshData.subscribe(onNext: { [weak self] wrappedModel in
            self?.dataSource.newData(from: wrappedModel)
            self?.tableNode.reloadData()
        }).disposed(by: disposeBag)

        viewModel.output.moreData.subscribe(onNext: { [weak self] wrappedModel in
            if let insertIndexPath = self?.dataSource.moreData(from: wrappedModel), !insertIndexPath.isEmpty {
                self?.tableNode.insertRows(at: insertIndexPath, with: UITableView.RowAnimation.automatic)
            }
        }).disposed(by: disposeBag)

        // etc...}}Copy the code

DataSource: The ASTableNode (UITableView) splits the DataSource to store and process Model, complies with the ASTableDataSource protocol, and further subdivides the responsibilities of the VM layer for easy maintenance.

That’s all for now, more logic processing in cellNode and cellNodeModel is similar.

Note: recently I was told not to call myself MVVM architecture, but the data processing layer still uses XxxNodeMode/ XxxViewModel name.

Model: Direct JSON parsing using struct and Codable. Note that implementing similar interface effects using UITableView may require additional storage properties and Decoder logic code to be added to the Model.

# Moya

Nuggets in the relevant technical articles are also many, here do not explain.

# Related knowledge points

# ASImageNode + Kingfrisher

It is not difficult to use third-party image libraries for ASImageNode, especially for protocol-based encapsulation Kingfirsh. Here is a simple extension:

import Kingfisher
import AsyncDisplayKit


extension ASImageNode: KingfisherCompatible {}

public extension KingfisherWrapper where Base: ASImageNode {

    func setImage(
        with source: Resource? .placeholder: UIImage? = nil.failureImage: UIImage? = nil.options: KingfisherOptionsInfo? = nil.progressBlock: DownloadProgressBlock? = nil)
    {
        guard let source = source else {
            self.base.image = placeholder ?? failureImage
            return
        }

        KingfisherManager.shared.retrieveImage(with: source, options: options, progressBlock: progressBlock, downloadTaskUpdated: nil) { result in
            switch result {
            case .success(let retrieveResult):
                self.base.image = retrieveResult.image
            case .failure(_) :self.base.image = failureImage ?? placeholder
            }
        }
    }
}
Copy the code

Note: I haven’t seen the source code of SDWebImage for a long time. Recently, I found that SDWebImage also started to use the default class reconstruction of protocol plus implementation protocol. Is it the reason I haven’t paid attention to it for a long time 😅?

# other

  • makeASControlsupportRxSwift
  • Regular (regx) expression inSwiftThe use of
  • Namespace creation
  • etc…

See the source code for details.

# About source code

Source code has been removed from the interface, if you have the ability to grab the interface (too simple operation) please in

  1. Scenedelegate. swift replaces rootVC with HomeTabBarController:

    func scene(_ scene: UIScene.willConnectTo session: UISceneSession.options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
    
        guard let windowScene = (scene as? UIWindowScene) else { return }
        window = UIWindow.init(frame: windowScene.coordinateSpace.bounds)
        window?.windowScene = windowScene
    
        //let rootVC = HomeTabBarController()
        //let rootVC = UINavigationController(rootViewController: SimpleRegxViewController())
        let rootVC = UINavigationController(rootViewController: TextureDemoViewController())
        window?.rootViewController = rootVC
        window?.makeKeyAndVisible()
    
        UIWindow.setupLayoutFitInfo()
    }
    Copy the code
  2. Xtnetworkservice. swift replaces jjBaseUrl and var path{XXX} with the corresponding interface.

If you just need to learn the Texture layout, etc., the xxx.json file is also included with the source code for your local use.

# ramble about

Texture is also used in the iOS client interface for Nuggets 2020. The boiling point page of Texture surprised me when I first saw it, and that’s when I started using it. Its CSS-like FlexBox layout idea is fascinating. The iOS version of the nuggets has undergone a major refactoring that removes a number of third-party libraries, including Texture, one of the manufacturers that used it in its popular App, 😞. Let’s hope the Nuggets iOS team shares the pros and cons of Texture.