Ответ 1
Итак, я закончил создание своего ответа. Он принимает другой подход, чем другие ответы, так что несите меня.
Вместо добавления представления контейнера то, что я понял, было бы лучшим способом создать подкласс UIViewController (который я назвал CircleDisplayViewController). Тогда все ваши VC, которые должны иметь эту функциональность, могут наследовать от него (а не от UIViewController).
Таким образом, вся ваша логика для представления и удаления ResultViewController обрабатывается в одном месте и может использоваться в любом месте вашего приложения.
То, как ваши VC могут использовать это, выглядит так:
class AnyViewController: CircleDisplayViewController {
/* Only inherit from CircleDisplayViewController,
otherwise you inherit from UIViewController twice */
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func showCircle(_ sender: UIButton) {
openCircle(withCenter: sender.center, radius: nil, resultDataSource: calculator!.iterateWPItems())
//I'll get to this stuff in just a minute
//Edit: from talking to Bright Future in chat I saw that resultViewController needs to be setup with calculator!.iterateWPItems()
}
}
Где showCircle представит ваш ResultViewController с помощью передающего делегата с центром круга в отправляющем центре UIButtons.
Подкласс CircleDisplayViewController таков:
class CircleDisplayViewController: UIViewController, UIViewControllerTransitioningDelegate, ResultDelegate {
private enum CircleState {
case collapsed, visible
}
private var circleState: CircleState = .collapsed
private var resultViewController: ResultViewController!
private lazy var transition = CircularTransition()
func openCircle(withCenter center: CGPoint, radius: CGFloat?, resultDataSource: ([Items], Int, String)) {
let circleCollapsed = (circleState == .collapsed)
DispatchQueue.main.async { () -> Void in
if circleCollapsed {
self.addCircle(withCenter: center, radius: radius, resultDataSource: resultDataSource)
}
}
}
private func addCircle(withCenter circleCenter: CGPoint, radius: CGFloat?, resultDataSource: ([Items], Int, String])) {
var circleRadius: CGFloat!
if radius == nil {
circleRadius = view.frame.size.height/2.0
} else {
circleRadius = radius
}
//instantiate resultViewController here, and setup delegate etc.
resultViewController = UIStoryboard.resultViewController()
resultViewController.transitioningDelegate = self
resultViewController.delegate = self
resultViewController.modalPresentationStyle = .custom
//setup any values for resultViewController here
resultViewController.dataSource = resultDataSource
//then set the frame of resultViewController (while also setting endFrame)
let resultOrigin = CGPoint(x: 0.0, y: circleCenter.y - circleRadius)
let resultSize = CGSize(width: view.frame.size.width, height: (view.frame.size.height - circleCenter.y) + circleRadius)
resultViewController.view.frame = CGRect(origin: resultOrigin, size: resultSize)
resultViewController.endframe = CGRect(origin: resultOrigin, size: resultSize)
transition.circle = UIView()
transition.startingPoint = circleCenter
transition.radius = circleRadius
transition.circle.frame = circleFrame(radius: transition.radius, center: transition.startingPoint)
present(resultViewController, animated: true, completion: nil)
}
func collapseCircle() { //THIS IS THE RESULT DELEGATE FUNCTIONS
dismiss(animated: true) {
self.resultViewController = nil
}
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .dismiss
transition.circleColor = UIColor.red
return transition
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
transition.transitionMode = .present
transition.circleColor = UIColor.red
return transition
}
func circleFrame(radius: CGFloat, center: CGPoint) -> CGRect {
let circleOrigin = CGPoint(x: center.x - radius, y: center.y - radius)
let circleSize = CGSize(width: radius*2, height: radius*2)
return CGRect(origin: circleOrigin, size: circleSize)
}
}
public extension UIStoryboard {
class func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: Bundle.main) }
}
private extension UIStoryboard {
class func resultViewController() -> ResultViewController {
return mainStoryboard().instantiateViewController(withIdentifier: "/* Your ID for ResultViewController */") as! ResultViewController
}
}
Единственная функция, которая вызывается VC, которая наследуется от DisplayCircleViewController, - openCircle, openCircle имеет аргумент circleCenter (который должен быть вашим центром кнопок, который я предполагаю), необязательный аргумент радиуса (если это значение nil, то значение по умолчанию из половины высоты представления, а затем все, что вам нужно для настройки ResultViewController.
В функции addCircle есть некоторые важные вещи:
вы устанавливаете ResultViewController, но вы должны перед представлением (например, вы готовились к segue),
затем настройте кадр для него (я попытался сделать его областью круга, который виден, но он довольно груб здесь, возможно, стоит поиграть с ним),
то здесь я reset окружность перехода (а не в классе перехода), так что я мог бы установить здесь начальную точку круга, радиус и фрейм.
тогда просто нормальный подарок.
Если вы не установили идентификатор для ResultViewController, вам нужно это сделать (см. расширения UIStoryboard)
Я также изменил функции TransitioningDelegate, поэтому вы не устанавливаете центр круга, потому что, чтобы сохранить его общий, я ставлю эту ответственность на ViewController, который наследует от этого. (см. верхний бит кода)
Наконец, я изменил класс CircularTransition
Я добавил переменную:
var radius: CGFloat = 0.0 //set in the addCircle function above
и изменено animateTransition:
(удалены прокомментированные строки):
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
if transitionMode == .present {
if let presentedView = transitionContext.view(forKey: UITransitionContextViewKey.to) {
...
// circle = UIView()
// circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
circle.layer.cornerRadius = radius
...
}
} else {
if let returningView = transitionContext.view(forKey: UITransitionContextViewKey.from) {
...
// circle.frame = frameForCircle(withViewCenter: viewCenter, size: viewSize, startPoint: startingPoint)
...
}
}
}
Наконец, я сделал протокол, чтобы ResultViewController мог отклонить круг
protocol ResultDelegate: class {
func collapseCircle()
}
class ResultViewController: UIViewController {
weak var delegate: ResultDelegate!
var endFrame: CGRect!
var dataSource: ([Items], Int, String)! // same as in Bright Future case
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidLayoutSubviews() {
if endFrame != nil {
view.frame = endFrame
}
}
@IBAction func closeResult(_ sender: UIButton) {
delegate.collapseCircle()
}
}
Это оказалось довольно огромным ответом, извините за это, я написал его немного поспешно, поэтому, если что-то неясно, просто скажите.
Надеюсь, это поможет!
Изменить: я нашел проблему, iOS 10 изменил способ отображения макетов, поэтому, чтобы исправить это, я добавил свойство endFrame в ResultViewController и установил его в виде view в viewDidLayoutSubviews. Я также устанавливаю как frame, так и endFrame одновременно в addCircle. Я изменил код выше, чтобы отразить изменения. Это не идеально, но я еще раз посмотрю, есть ли лучшее исправление.
Изменить: это то, что выглядит для меня открытым.