DressUp.swift 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import SwiftUI
  2. import Spine
  3. import SpineCppLite
  4. struct DressUp: View {
  5. @StateObject
  6. var model = DressUpModel()
  7. var body: some View {
  8. HStack(spacing: 0) {
  9. List {
  10. ForEach(model.skinImages.keys.sorted(), id: \.self) { skinName in
  11. let rawImageData = model.skinImages[skinName]!
  12. Button(action: { model.toggleSkin(skinName: skinName) }) {
  13. Image(uiImage: UIImage(cgImage: rawImageData))
  14. .resizable()
  15. .scaledToFit()
  16. .frame(width: model.thumbnailSize.width, height: model.thumbnailSize.height)
  17. .grayscale(model.selectedSkins[skinName] == true ? 0.0 : 1.0)
  18. }
  19. }
  20. }
  21. .listStyle(.plain)
  22. Divider()
  23. if let drawable = model.drawable {
  24. SpineView(
  25. from: .drawable(drawable),
  26. controller: model.controller,
  27. boundsProvider: SkinAndAnimationBounds(skins: ["full-skins/girl"])
  28. )
  29. } else {
  30. Spacer()
  31. }
  32. }
  33. .navigationTitle("Dress Up")
  34. .navigationBarTitleDisplayMode(.inline)
  35. }
  36. }
  37. #Preview {
  38. DressUp()
  39. }
  40. final class DressUpModel: ObservableObject {
  41. let thumbnailSize = CGSize(width: 200, height: 200)
  42. @Published
  43. var controller: SpineController
  44. @Published
  45. var drawable: SkeletonDrawableWrapper?
  46. @Published
  47. var skinImages = [String: CGImage]()
  48. @Published
  49. var selectedSkins = [String: Bool]()
  50. private var customSkin: Skin?
  51. init() {
  52. controller = SpineController(
  53. onInitialized: { controller in
  54. controller.animationState.setAnimationByName(
  55. trackIndex: 0,
  56. animationName: "dance",
  57. loop: true
  58. )
  59. },
  60. disposeDrawableOnDeInit: false
  61. )
  62. Task.detached(priority: .high) {
  63. let drawable = try await SkeletonDrawableWrapper.fromBundle(
  64. atlasFileName: "mix-and-match-pma.atlas",
  65. skeletonFileName: "mix-and-match-pro.skel"
  66. )
  67. try await MainActor.run {
  68. for skin in drawable.skeletonData.skins {
  69. if skin.name == "default" { continue }
  70. let skeleton = drawable.skeleton
  71. skeleton.skin = skin
  72. skeleton.setToSetupPose()
  73. skeleton.update(delta: 0)
  74. skeleton.updateWorldTransform(physics: SPINE_PHYSICS_UPDATE)
  75. try skin.name.flatMap { skinName in
  76. self.skinImages[skinName] = try drawable.renderToImage(
  77. size: self.thumbnailSize,
  78. backgroundColor: .white,
  79. scaleFactor: UIScreen.main.scale
  80. )
  81. self.selectedSkins[skinName] = false
  82. }
  83. }
  84. self.toggleSkin(skinName: "full-skins/girl", drawable: drawable)
  85. self.drawable = drawable
  86. }
  87. }
  88. }
  89. deinit {
  90. drawable?.dispose()
  91. customSkin?.dispose()
  92. }
  93. func toggleSkin(skinName: String) {
  94. if let drawable {
  95. toggleSkin(skinName: skinName, drawable: drawable)
  96. }
  97. }
  98. func toggleSkin(skinName: String, drawable: SkeletonDrawableWrapper) {
  99. selectedSkins[skinName] = !(selectedSkins[skinName] ?? false)
  100. customSkin?.dispose()
  101. customSkin = Skin.create(name: "custom-skin")
  102. for skinName in selectedSkins.keys {
  103. if selectedSkins[skinName] == true {
  104. if let skin = drawable.skeletonData.findSkin(name: skinName) {
  105. customSkin?.addSkin(other: skin)
  106. }
  107. }
  108. }
  109. drawable.skeleton.skin = customSkin
  110. drawable.skeleton.setToSetupPose()
  111. }
  112. }