Userspace apps don't need a controlling tty, but its very hard for a userspace app not to have one due to the way most programs are started (from a tty or pseudo-tty).
Normally most apps have one or more of stdin,stdout and stderr connected to a real life tty somewhere (even if just for error logging).

You are right in that if you call setsid() it creates a new process group which breaks the connection from the process group it used to be part of.

But heres the kicker - if the new process group has ANY tty file descriptors open then that process is still listed as having a controlling tty.

If a process piror to a setsid call closed all open tty file descriptors, then subsequent to the setsid() call opens a (new) tty, then that tty will become the controlling tty for that app. You can prevent this but its requires some special IOCTLs to do so.

If the app never opens another tty in its life it will not have a controlling tty - ps normally shows this by putting a '-' as the tty name - thats why some processes like init show up this way.

Now, before the fork() is called a signal(SIGHUP,SIG_IGNORE) should be done to tell the kernel not to bother the app (or its children) with SIGHUPs unless they want to enable them.

Then if the user app is well behaved (or doesn't know about signals), it will only enable SIGHUP again in its code if its has a handler for the signal and wants to be told when its process group leader (parent or grand parent etc) has died.

Without a controlling tty then the kernel will not normally kill the child processes when the process who started them exits, when a process ignoring sighup becomes orphaned by its process group leader dying, then the kernel changes its parent pid to 1 (meaning init), which is why lots of background processes have 1 as the Parent PID in ps.
This is what nohup does as nohup launches the program then exits, forcing the process started to be inherited by init.

All this means is that in general userspace apps don't need to bother with signals esp. SIGHUP provided its handled for them by the launching program (e.g. kftpd / kim's code).