I am unfortunately faced with a large legacy code base in which Storyboards are heavily used. Now, for some reason, the entire app window is resized if a certain View Controller becomes visible.
The issue: Apparently, there aren't any conflicting layout constraints (no LAYOUT_CONSTRAINTS_NOT_SATISFIABLE errors are raised on display of the view controller).
There are also no calls to setFrame on the corresponding window.
So, how do I debug this? Capturing the view hierarchy didn't provide any helpful insights, and ideally I could just force the window to not resize (due to possible constraint errors).
Is there any way to achieve something like this? If not, how can I go about debugging this?
Any help on this would be greatly appreciated.
Post
Replies
Boosts
Views
Activity
Problem
I am trying to send out a broadcast using NWConnection and then listen for responses using NWListener on port 50913. Although the broadcast is sent out correctly (= no error is thrown upon sending), I only get responses to my broadcast from what I suppose are the network interfaces of my own MacBook. In other words, it seems like the broadcast is never really submitted to the network.
Context
I don't have in-depth knowledge about the behavior of UDP which is why I am confused about this behavior. I've been reading online about this and couldn't find anything really related to the behavior I am experiencing. I've also looked at this developer forums entry and implemented the broadcast accordingly. The response from @meaton does not suggest that broadcasts are not supported by NWConnection (which is what I thought to be the culprit initially), and I am not getting the error they are talking about in their post, but a behavior that is entirely different.
Does anyone know what is wrong with my implementation?
Code
final public class BroadcastDiscoveryEngine {
private let logger: Logger = Logger.init(for: BroadcastDiscoveryEngine.self)
private let broadcastConnection: NWConnection
private let broadcastResponseListener: NWListener
private let responseParser: BroadcastResponseParser = BroadcastResponseParser()
private var discoveryContinuation: AsyncStream<Discovery>.Continuation? = nil
init() throws {
let parameters = NWParameters.udp
parameters.allowLocalEndpointReuse = true
parameters.allowFastOpen = true
parameters.includePeerToPeer = true
broadcastConnection = NWConnection(host: .ipv4(.broadcast), port: .init(integerLiteral: 50913), using: parameters)
broadcastResponseListener = try NWListener(using: parameters, on: 50913)
}
func startBroadcast(continuation: AsyncStream<Discovery>.Continuation) {
discoveryContinuation = continuation
broadcastConnection.stateUpdateHandler = handleBroadcastConnectionStateUpdate(state:)
broadcastConnection.start(queue: .global(qos: .default))
startBroadcastListener()
}
func stopBroadcast() {
broadcastConnection.cancel()
broadcastResponseListener.cancel()
}
private func sendBroadcastMessage() {
broadcastConnection.send(content: "my_broadcast_message".data(using: .utf8), completion: .contentProcessed({ error in
if let error = error {
self.logger.error("Sending broadcast message failed with error: \(error.debugDescription, privacy: .public)")
self.broadcastConnection.cancel()
self.broadcastResponseListener.cancel()
}
self.logger.info("Broadcast message sent.")
}))
}
private func handleBroadcastConnectionStateUpdate(state: NWConnection.State) {
switch state {
// shortened other cases since only logging occurs
case .ready:
logger.info("Broadcast connection established, ready to send and receive data.")
sendBroadcastMessage()
}
}
}
extension BroadcastDiscoveryEngine {
private func startBroadcastListener() {
broadcastResponseListener.stateUpdateHandler = handleBroadcastResponseListenerStateUpdate(state:)
broadcastResponseListener.newConnectionHandler = handleIncomingConnection(connection:)
broadcastResponseListener.start(queue: .global(qos: .default))
}
private func handleBroadcastResponseListenerStateUpdate(state: NWListener.State) {
switch state {
// shortened cases since only logging occurs
}
}
private func handleIncomingConnection(connection: NWConnection) {
connection.stateUpdateHandler = { state in self.handleIncomingConnectionStateUpdate(connection: connection, state: state) }
connection.start(queue: .global(qos: .default))
}
private func handleIncomingConnectionStateUpdate(connection: NWConnection, state: NWConnection.State) {
switch state {
// shortened other cases since only logging occurs
case .ready:
logger.info("Incoming connection (\(connection.debugDescription, privacy: .public) established, ready to send and receive data.")
connection.receiveMessage { content, contentContext, isComplete, error in
self.receiveBroadcastResponse(connection: connection, content: content, contentContext: contentContext, isComplete: isComplete, error: error)
}
}
}
private func receiveBroadcastResponse(connection: NWConnection, content: Data?, contentContext: NWConnection.ContentContext?, isComplete: Bool, error: NWError?) {
// shortened: handles parsing accordingly and then cancels connection
connection.cancel()
}
}
Problem:
I am trying to create my own custom search field with a desired growing animation (if you click on it), and a shrinking animation when the user taps out.
The animation behaves weirdly since it moves out of the right screen bounds when shrinking, even though the text field/search bar's right anchor is not modified.
Like so:
Notice how the right side of the search bar briefly moves outside of the visible screen bounds during the animation.
Expected behavior:
The search bar should smoothly grow/shrink without moving the right edge position of the text field, i.e. have the right anchor stay pinned.
What you see in above gif is built using the following code (by subclassing a UITextField):
public class MySearchBar: UITextField {
private var preAnimationWidth: NSLayoutConstraint?
private var postAnimationWidth: NSLayoutConstraint?
public override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = Theme.GRAY800
self.borderStyle = .roundedRect
self.layer.masksToBounds = true
self.clipsToBounds = true
self.autocorrectionType = .no
self.font = FontFamily.ProximaNova.regular.font(size: 16)
self.textColor = .white
self.attributedPlaceholder = NSAttributedString(string: "Search", attributes: [.foregroundColor : Theme.GRAY400, .font: FontFamily.ProximaNova.regular.font(size: 16)])
// some further appearance configurations
}
public func setupGrowAnimation(initialWidth: NSLayoutConstraint, grownWidth: NSLayoutConstraint, height: CGFloat) {
preAnimationWidth = initialWidth
postAnimationWidth = grownWidth
self.layer.borderWidth = 0
self.layer.cornerRadius = height / 2
}
// growButton is called when the textfield becomes active, i.e. the user taps on it.
public func growButton() {
guard let preAnimationWidth = preAnimationWidth, let postAnimationWidth = postAnimationWidth else { return }
UIView.animate(withDuration: 0.2) {
preAnimationWidth.isActive = false
postAnimationWidth.isActive = true
self.layer.borderColor = Theme.GRAY600.cgColor
self.layer.borderWidth = 2
self.layer.cornerRadius = 8
self.layoutIfNeeded()
}
}
// shrinkButton is called whenever the textfield resigns its first responder state, i.e. the user clicks out of it.
public func shrinkButton() {
guard let preAnimationWidth = preAnimationWidth, let postAnimationWidth = postAnimationWidth else { return }
UIView.animate(withDuration: 0.2) {
postAnimationWidth.isActive = false
preAnimationWidth.isActive = true
self.layer.borderWidth = 0
self.layer.borderColor = .none
self.layer.cornerRadius = self.frame.height / 2
self.layoutIfNeeded()
}
}
}
And this is how the search bar is initialized in my viewDidLoad:
override func viewDidLoad() {
let containerView = UIView()
let searchBar = MySearchBar()
searchBar.addTarget(self, action: #selector(searchBarChangedEntry(_:)), for: .editingChanged)
searchBar.addTarget(self, action: #selector(searchBarEndedEditing(_:)), for: .editingDidEnd)
searchBar.translatesAutoresizingMaskIntoConstraints = false
let initialWidth = searchBar.widthAnchor.constraint(equalToConstant: 100)
let expandedWidth = searchBar.widthAnchor.constraint(equalTo: containerView.widthAnchor, constant: -32)
searchBar.setupGrowAnimation(initialWidth: initialWidth, grownWidth: expandedWidth, height: 44)
containerView.addSubview(searchBar)
stackView.insertArrangedSubview(containerView, at: 0)
NSLayoutConstraint.activate([
containerView.heightAnchor.constraint(equalToConstant: 44),
containerView.widthAnchor.constraint(equalTo: self.stackView.widthAnchor),
searchBar.heightAnchor.constraint(equalTo: containerView.heightAnchor),
initialWidth,
searchBar.rightAnchor.constraint(equalTo: containerView.rightAnchor, constant: -16)
])
self.stackView.setCustomSpacing(12, after: containerView)
}
The search bar is part of a container view which, in turn, is the first (top) arranged subview of a stack view covering the entire screen's safeAreaLayout rectangle
What I already tried:
I have to perform the animation using constraints, and I've tried to animate it without using the width anchor (e.g. by animating the leftAnchor's constant). Nothing worked so far.
Upon googling, I couldn't really find anything helpful that would help me find a solution to this problem, which is why I am trying my luck here.
I do have to admit that I am not proficient with animations of iOS at all - so please bear with me if this is a simple mistake to fix.
So, why does the search bar behave that way? And how can I fix this?
I am trying to create a simple NSTextField where the entire text content should be a link. The text field is created/positioned in Interface Builder and later propagated with actual data by the view controller. However, the text field does not display my attributed string.
In order to keep things neat and tidy, I subclassed NSTextField like so:
public class MyLink: NSTextField {
private var linkRange: NSRange?
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
public func setLinkContent(text: String, to url: URL, range: NSRange?) {
allowsEditingTextAttributes = true
isSelectable = true
let attributedLink = NSMutableAttributedString(string: text)
self.linkRange = range ?? NSRange(location: 0, length: text.count)
attributedLink.addAttribute(.link, value: url, range: self.linkRange!)
attributedLink.addAttributes([
.foregroundColor: NSColor.red,
.underlineStyle: NSUnderlineStyle.single,
.cursor: NSCursor.pointingHand,
.font: NSFont.systemFont(ofSize: 16)
], range: self.linkRange!)
self.attributedStringValue = attributedLink
}
}
To put things in perspective, the view controller calls setLinkContent to propagate the text field data. And after that - nothing (see image). You see that the text field is there because of the blue background color I set, but the actual text is not propagated.
Configuration of the TextField in IB: