1

我在一个有很多 UI 控件的 iOS 应用程序中有 VC。我现在需要在特定状态下替换或“模拟”其中一些控件。在某些情况下,这只是禁用按钮操作,但在某些情况下,发生的操作需要用完全不同的东西替换。

我真的不喜欢在代码库中到处乱扔这种检查的想法。

if condition {
  ...Special/disabled functionality
} else {
  ...Normal functionality
}

在 Android 中,我可以将每个 Fragment/Activity 子类化并在那里构建功能,然后在插入 Fragment 或启动 Activity 时执行 if/else。

但是在带有 Storyboards/IBActions 和 Segues 的 iOS 上,UI 和 VC 确实是紧密耦合的。您最终要么复制 UI 视图,要么向已经很大的 VC 添加大量繁琐的代码。

在 iOS 中处理这个问题的最佳方法是什么?

我想避免做的示例代码:

//Before:
class SomeViewController : UIViewController {
  @IBAction onSomeButton() {
    checkSomeState()
    doANetworkRequest(() -> {
       someCompletionHandler()
       updatesTheUI()
    }
    updateTheUIWhileLoading()
  }

  @IBAction onSomeOtherButton() {
    checkAnotherState()
    updateUI()
  }
}
//After:
class SomeViewController : UIViewController {
  @IBAction onSomeButton() {
    if specialState {
      doSomethingSimpler()
    } else {
      checkSomeState()
      doANetworkRequest(() -> {
         someCompletionHandler()
         updatesTheUI()
      }
      updateTheUIWhileLoading()
    }
  }

  @IBAction onSomeOtherButton() {
    if specialState {
      return // Do nothing
    } else {
      checkAnotherState()
      updateUI()
    }
  }
}
4

1 回答 1

1

我建议使用MVVM (Model - View - ViewModel) 模式。您将 传递ViewModel给您的控制器并将所有操作委托给它。您还可以使用它来设置视图样式并决定是否应该隐藏或禁用其中的一些视图等。

让我们想象一个购物应用程序,您的专业用户可以在其中获得 10% 的折扣,并且可以使用免费送货选项。

protocol PaymentScreenViewModelProtocol {
    var regularPriceString: String { get }
    var discountedPriceString: String? { get }
    var isFreeShippingAvailable: Bool { get }

    func userSelectedFreeShipping()
    func buy()
}

class StandardUserPaymentScreenViewModel: PaymentScreenViewModelProtocol {
    let regularPriceString: String = "20"
    let discountedPriceString: String? = nil
    let isFreeShippingAvailable: Bool = false

    func userSelectedFreeShipping() {
        // standard users cannot use free shipping!
    }

    func buy() {
        // process buying
    }
}

class ProUserPaymentScreenViewModel: PaymentScreenViewModelProtocol {
    let regularPriceString: String = "20"
    let discountedPriceString: String? = "18"
    let isFreeShippingAvailable: Bool = true

    func userSelectedFreeShipping() {
        // process selection of free shipping
    }

    func buy() {
        // process buying
    }
}

class PaymentViewController: UIViewController {

    @IBOutlet weak var priceLabel: UILabel!
    @IBOutlet weak var discountedPriceLabel: UILabel!
    @IBOutlet weak var freeShippingButton: UIButton!

    var viewModel: PaymentScreenViewModelProtocol

    override func viewDidLoad() {
        super.viewDidLoad()

        priceLabel.text = viewModel.regularPriceString
        discountedPriceLabel.text = viewModel.discountedPriceString
        freeShippingButton.isHidden = !viewModel.isFreeShippingAvailable
    }

    @IBAction func userDidPressFreeShippingButton() {
        viewModel.userSelectedFreeShipping()
    }

    @IBAction func userDidPressBuy() {
        viewModel.buy()
    }
}

这种方法让您将逻辑与视图分离。测试这个逻辑也更容易。
要考虑和决定的一件事是如何将视图模型注入视图控制器。我可以看到三种可能性:

  1. Via init- 你提供了一个需要传递视图模型的自定义初始化程序。这将意味着您将无法使用segue's 或storyboards(您将能够使用xibs)。这将使您的视图模型是非可选的。
  2. 通过具有默认实现的属性设置 - 如果您提供某种形式的视图模型的默认/空实现,您可以将其用作它的默认值,并稍后设置正确的实现(例如在 中prepareForSegue)。这使您可以使用segues 和storyboards 并使视图模型成为非可选的(它只是增加了额外的空实现的开销)。
  3. 通过没有默认实现的属性设置 - 这基本上意味着您的视图模型将需要是一个可选的,并且您几乎每次访问它时都必须检查它。
于 2018-09-06T08:16:04.893 回答